Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions cmd/testrunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,16 @@ func testTypeCommandActionFactory(runner testrunner.TestRunner) cobraext.Command
}
}

testFolders, err = testrunner.FindTestFolders(packageRootPath, dataStreams, testType)
if err != nil {
return errors.Wrap(err, "unable to determine test folder paths")
if runner.TestFolderRequired() {
testFolders, err = testrunner.FindTestFolders(packageRootPath, dataStreams, testType)
if err != nil {
return errors.Wrap(err, "unable to determine test folder paths")
}
} else {
testFolders, err = testrunner.AssumeTestFolders(packageRootPath, dataStreams, testType)
if err != nil {
return errors.Wrap(err, "unable to assume test folder paths")
}
}

if failOnMissing && len(testFolders) == 0 {
Expand Down
17 changes: 14 additions & 3 deletions internal/fields/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,23 @@ import (

// Validator is responsible for fields validation.
type Validator struct {
schema []FieldDefinition
numericKeywordFields map[string]struct{}
schema []FieldDefinition

defaultNumericConvertion bool
Comment thread
mtojek marked this conversation as resolved.
Outdated
numericKeywordFields map[string]struct{}
}

// ValidatorOption represents an optional flag that can be passed to CreateValidatorForDataStream.
type ValidatorOption func(*Validator) error

// WithDefaultNumericConversion configures the validator to accept defined keyword (or constant_keyword) fields as numeric-type.
func WithDefaultNumericConversion() ValidatorOption {
return func(v *Validator) error {
v.defaultNumericConvertion = true
return nil
}
}

// WithNumericKeywordFields configures the validator to accept specific fields to have numeric-type
// while defined as keyword or constant_keyword.
func WithNumericKeywordFields(fields []string) ValidatorOption {
Expand Down Expand Up @@ -155,7 +165,8 @@ func (v *Validator) validateScalarElement(key string, val interface{}) error {
}

// Convert numeric keyword fields to string for validation.
if _, found := v.numericKeywordFields[key]; found && isNumericKeyword(*definition, val) {
_, found := v.numericKeywordFields[key]
if (found || v.defaultNumericConvertion) && isNumericKeyword(*definition, val) {
val = fmt.Sprintf("%q", val)
}

Expand Down
4 changes: 4 additions & 0 deletions internal/testrunner/runners/asset/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ func (r *runner) TearDown() error {
return nil
}

func (r *runner) TestFolderRequired() bool {
return false
}

func findActualAsset(actualAssets []packages.Asset, expectedAsset packages.Asset) bool {
for _, a := range actualAssets {
if a.Type == expectedAsset.Type && a.ID == expectedAsset.ID {
Expand Down
4 changes: 4 additions & 0 deletions internal/testrunner/runners/pipeline/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ type runner struct {
options testrunner.TestOptions
}

func (r *runner) TestFolderRequired() bool {
return true
}

// Type returns the type of test that can be run by this test runner.
func (r *runner) Type() testrunner.TestType {
return TestType
Expand Down
1 change: 1 addition & 0 deletions internal/testrunner/runners/runners.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ import (
// Registered test runners
_ "github.com/elastic/elastic-package/internal/testrunner/runners/asset"
_ "github.com/elastic/elastic-package/internal/testrunner/runners/pipeline"
_ "github.com/elastic/elastic-package/internal/testrunner/runners/static"
_ "github.com/elastic/elastic-package/internal/testrunner/runners/system"
)
130 changes: 130 additions & 0 deletions internal/testrunner/runners/static/runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package static

import (
"io/ioutil"
"os"
"path/filepath"

"github.com/pkg/errors"

"github.com/elastic/elastic-package/internal/fields"
"github.com/elastic/elastic-package/internal/logger"
"github.com/elastic/elastic-package/internal/testrunner"
)

const sampleEventJSON = "sample_event.json"

type runner struct {
options testrunner.TestOptions
}

var _ testrunner.TestRunner = new(runner)

func init() {
testrunner.RegisterRunner(&runner{})
}

const (
// TestType defining asset loading tests
TestType testrunner.TestType = "static"
)

func (r runner) Type() testrunner.TestType {
return TestType
}

func (r runner) String() string {
return "static files"
}

func (r runner) Run(options testrunner.TestOptions) ([]testrunner.TestResult, error) {
r.options = options
return r.run()
}

func (r runner) run() ([]testrunner.TestResult, error) {
result := testrunner.NewResultComposer(testrunner.TestResult{
TestType: TestType,
Package: r.options.TestFolder.Package,
DataStream: r.options.TestFolder.DataStream,
})

testConfig, err := newConfig(r.options.TestFolder.Path)
if err != nil {
return result.WithError(errors.Wrap(err, "unable to load asset loading test config file"))
}

if testConfig != nil && testConfig.Skip != nil {
logger.Warnf("skipping %s test for %s: %s (details: %s)",
TestType, r.options.TestFolder.Package,
testConfig.Skip.Reason, testConfig.Skip.Link.String())
return result.WithSkip(testConfig.Skip)
}

var results []testrunner.TestResult
results = append(results, r.verifySampleEvent()...)
return results, nil
}

func (r runner) verifySampleEvent() []testrunner.TestResult {
dataStreamPath := filepath.Join(r.options.PackageRootPath, "data_stream", r.options.TestFolder.DataStream)
sampleEventPath := filepath.Join(dataStreamPath, sampleEventJSON)
_, err := os.Stat(sampleEventPath)
if os.IsNotExist(err) {
return []testrunner.TestResult{} // nothing to succeed, nothing to skip
}

resultComposer := testrunner.NewResultComposer(testrunner.TestResult{
Name: "Verify " + sampleEventJSON,
TestType: TestType,
Package: r.options.TestFolder.Package,
DataStream: r.options.TestFolder.DataStream,
})

if err != nil {
results, _ := resultComposer.WithError(errors.Wrap(err, "stat file failed"))
return results
}

fieldsValidator, err := fields.CreateValidatorForDataStream(
dataStreamPath,
fields.WithDefaultNumericConversion())
if err != nil {
results, _ := resultComposer.WithError(errors.Wrap(err, "creating fields validator for data stream failed"))
return results
}

content, err := ioutil.ReadFile(sampleEventPath)
if err != nil {
results, _ := resultComposer.WithError(errors.Wrap(err, "can't read file"))
return results
}

multiErr := fieldsValidator.ValidateDocumentBody(content)
if len(multiErr) > 0 {
results, _ := resultComposer.WithError(testrunner.ErrTestCaseFailed{
Reason: "one or more errors found in document",
Details: multiErr.Error(),
})
return results
}

results, _ := resultComposer.WithSuccess()
return results
}

func (r runner) TearDown() error {
return nil // it's a static test runner, no state is stored
}

func (r runner) CanRunPerDataStream() bool {
return true
}

func (r *runner) TestFolderRequired() bool {
return false
}
47 changes: 47 additions & 0 deletions internal/testrunner/runners/static/test_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package static

import (
"io/ioutil"
"os"
"path/filepath"

"github.com/elastic/go-ucfg"
"github.com/elastic/go-ucfg/yaml"
"github.com/pkg/errors"

"github.com/elastic/elastic-package/internal/testrunner"
)

type testConfig struct {
testrunner.SkippableConfig `config:",inline"`
}

func newConfig(staticTestFolderPath string) (*testConfig, error) {
configFilePath := filepath.Join(staticTestFolderPath, "config.yml")

@ycombinator ycombinator Feb 24, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we define this as part of the package spec, as an optional file?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I totally forgot about the package-spec.

BTW Should I wait with this PR until you'll merge #260 or do you prefer the other way round?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just merged #260 so you'll need to rebase this PR on top of master.


// Test configuration file is optional for static loading tests. If it
// doesn't exist, we can return early.
if _, err := os.Stat(configFilePath); os.IsNotExist(err) {
return nil, nil
}

data, err := ioutil.ReadFile(configFilePath)
if err != nil {
return nil, errors.Wrapf(err, "could not load static loading test configuration file: %s", configFilePath)
}

var c testConfig
cfg, err := yaml.NewConfig(data, ucfg.PathSep("."))
if err != nil {
return nil, errors.Wrapf(err, "unable to load static loading test configuration file: %s", configFilePath)
}
if err := cfg.Unpack(&c); err != nil {
return nil, errors.Wrapf(err, "unable to unpack static loading test configuration file: %s", configFilePath)
}

return &c, nil
}
4 changes: 4 additions & 0 deletions internal/testrunner/runners/system/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ func (r *runner) CanRunPerDataStream() bool {
return true
}

func (r *runner) TestFolderRequired() bool {
return true
}

// Run runs the system tests defined under the given folder
func (r *runner) Run(options testrunner.TestOptions) ([]testrunner.TestResult, error) {
r.options = options
Expand Down
41 changes: 41 additions & 0 deletions internal/testrunner/testrunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package testrunner

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
Expand Down Expand Up @@ -47,6 +49,8 @@ type TestRunner interface {
TearDown() error

CanRunPerDataStream() bool

TestFolderRequired() bool
}

var runners = map[TestType]TestRunner{}
Expand Down Expand Up @@ -138,6 +142,43 @@ type TestFolder struct {
DataStream string
}

// AssumeTestFolders assumes potential test folders for the given package, data streams and test types.
func AssumeTestFolders(packageRootPath string, dataStreams []string, testType TestType) ([]TestFolder, error) {
// Expected folder structure:
// <packageRootPath>/
// data_stream/
// <dataStream>/

dataStreamsPath := filepath.Join(packageRootPath, "data_stream")

if dataStreams == nil || len(dataStreams) == 0 {
fileInfos, err := ioutil.ReadDir(dataStreamsPath)
if os.IsNotExist(err) {
return []TestFolder{}, nil // data streams defined
}
if err != nil {
return nil, errors.Wrapf(err, "can't read directory (path: %s)", dataStreamsPath)
}

for _, fi := range fileInfos {
if !fi.IsDir() {
continue
}
dataStreams = append(dataStreams, fi.Name())
}
}

var folders []TestFolder
for _, dataStream := range dataStreams {
folders = append(folders, TestFolder{
Path: filepath.Join(dataStreamsPath, dataStream, "_dev", "test", string(testType)),
Package: filepath.Base(packageRootPath),
DataStream: dataStream,
})
}
return folders, nil
}

// FindTestFolders finds test folders for the given package and, optionally, test type and data streams
func FindTestFolders(packageRootPath string, dataStreams []string, testType TestType) ([]TestFolder, error) {
// Expected folder structure:
Expand Down
17 changes: 0 additions & 17 deletions test/packages/apache/data_stream/status/sample_event.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
{
"@timestamp": "2020-06-24T10:19:48.005Z",
"@metadata": {
"beat": "metricbeat",
"type": "_doc",
"version": "8.0.0",
"raw_index": "metrics-apache.status-default"
},
"metricset": {
"name": "status",
"period": 10000
Expand Down Expand Up @@ -47,7 +41,6 @@
"idle": 74
},
"bytes_per_sec": 83.6986,
"hostname": "127.0.0.1:8088",
"uptime": {
"server_uptime": 1566,
"uptime": 1566
Expand All @@ -71,16 +64,6 @@
"dataset": "apache.status",
"module": "apache"
},
"dataset": {
"type": "metrics",
"name": "apache.status",
"namespace": "default"
},
"stream": {
"dataset": "apache.status",
"namespace": "default",
"type": "metrics"
},
"ecs": {
"version": "1.5.0"
},
Expand Down
Loading