Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,23 +150,29 @@ _Context: package_

Use this command to run tests on a package. Currently, there are two types of tests available.

#### Asset Loading Tests
Comment thread
mtojek marked this conversation as resolved.

These tests ensure that all the Elasticsearch and Kibana assets defined by your package get loaded up as expected.

For details on how to run asset loading tests for a package, see the [HOWTO guide](docs/howto/asset_testing.md).

#### Pipeline Tests

These tests allow you to exercise any Ingest Node Pipelines defined by your packages.

For details on how to configure and run pipeline tests for a package, see the [HOWTO guide](docs/howto/pipeline_testing.md).

#### System Tests
#### Static Tests

These tests allow you to test a package's ability to ingest data end-to-end.
These tests allow you to verify if all static resources of the package are valid, e.g. are all fields of the `sample_event.json` documented.
Comment thread
mtojek marked this conversation as resolved.
Outdated

For details on how to configure and run system tests for a package, see the [HOWTO guide](docs/howto/system_testing.md).
For details on how to run static tests for a package, see the [HOWTO guide](docs/howto/static_testing.md).

#### Asset Loading Tests
#### System Tests

These tests ensure that all the Elasticsearch and Kibana assets defined by your package get loaded up as expected.
These tests allow you to test a package's ability to ingest data end-to-end.

For details on how to run asset loading tests for a package, see the [HOWTO guide](docs/howto/asset_testing.md).
For details on how to configure and run system tests for a package, see the [HOWTO guide](docs/howto/system_testing.md).


### `elastic-package uninstall`
Expand Down
19 changes: 16 additions & 3 deletions cmd/testrunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,16 @@ import (

const testLongDescription = `Use this command to run tests on a package. Currently, the following types of tests are available:

Asset Loading Tests
These tests allow you to exercise installing a package to ensure that its assets are loaded into Elasticsearch and Kibana as expected.

Pipeline Tests
These tests allow you to exercise any Ingest Node Pipelines defined by your packages.
For details on how to configure pipeline test for a package, review the HOWTO guide (see: https://github.com/elastic/elastic-package/blob/master/docs/howto/pipeline_testing.md).

Static Tests
These tests allow you to verify if all static resources of the package are valid, e.g. are all fields of the sample_event.json documented.
Comment thread
mtojek marked this conversation as resolved.
Outdated

System Tests
These tests allow you to test a package's ability to ingest data end-to-end.
For details on how to configure amd run system tests, review the HOWTO guide (see: https://github.com/elastic/elastic-package/blob/master/docs/howto/system_testing.md).
Expand Down Expand Up @@ -126,9 +132,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
2 changes: 1 addition & 1 deletion docs/howto/asset_testing.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# HOWTO: Writing asset loading tests for a package
# HOWTO: Running asset loading tests for a package
Comment thread
mtojek marked this conversation as resolved.

## Introduction

Expand Down
28 changes: 28 additions & 0 deletions docs/howto/static_testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# HOWTO: Running static tests for a package

## Introduction

Static tests allow you to verify if all static resources of the package are valid, e.g. are all fields of the `sample_event.json` documented.
They don't require any additional configuration (unless you would like to skip them).

## Coverage

Static tests cover the following resources:

1. Sample event for a data stream - verification if the file uses only documented fields.

## Running static tests

Static tests don't require the Elastic stack to be up and running. Simply navigate to the package's root folder
(or any sub-folder under it) and run the following command.

```
elastic-package test static
```

If you want to run pipeline tests for **specific data streams** in a package, navigate to the package's root folder
(or any sub-folder under it) and run the following command.

```
elastic-package test static --data-streams <data stream 1>[,<data stream 2>,...]
```
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

defaultNumericConversion bool
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.defaultNumericConversion = 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.defaultNumericConversion) && 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
Loading