internal/testrunner/runners/static/tester.go (191 lines of code) (raw):

// 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 ( "context" "errors" "fmt" "os" "path/filepath" "github.com/elastic/elastic-package/internal/benchrunner/runners/stream" "github.com/elastic/elastic-package/internal/fields" "github.com/elastic/elastic-package/internal/logger" "github.com/elastic/elastic-package/internal/packages" "github.com/elastic/elastic-package/internal/signal" "github.com/elastic/elastic-package/internal/testrunner" ) type tester struct { testFolder testrunner.TestFolder packageRootPath string globalTestConfig testrunner.GlobalRunnerTestConfig withCoverage bool coverageType string } type StaticTesterOptions struct { TestFolder testrunner.TestFolder PackageRootPath string GlobalTestConfig testrunner.GlobalRunnerTestConfig WithCoverage bool CoverageType string } func NewStaticTester(options StaticTesterOptions) *tester { runner := tester{ testFolder: options.TestFolder, packageRootPath: options.PackageRootPath, globalTestConfig: options.GlobalTestConfig, withCoverage: options.WithCoverage, coverageType: options.CoverageType, } return &runner } // Ensures that runner implements testrunner.Tester interface var _ testrunner.Tester = new(tester) func (r tester) Type() testrunner.TestType { return TestType } func (r tester) String() string { return "static files" } // Parallel indicates if this tester can run in parallel or not. func (r tester) Parallel() bool { // Not supported yet parallel tests even if it is indicated in the global config r.globalTestConfig return false } func (r tester) Run(ctx context.Context) ([]testrunner.TestResult, error) { return r.run(ctx) } func (r tester) run(ctx context.Context) ([]testrunner.TestResult, error) { result := testrunner.NewResultComposer(testrunner.TestResult{ TestType: TestType, Package: r.testFolder.Package, DataStream: r.testFolder.DataStream, }) testConfig, err := newConfig(r.testFolder.Path) if err != nil { return result.WithError(fmt.Errorf("unable to load asset loading test config file: %w", err)) } skipConfigs := []*testrunner.SkipConfig{r.globalTestConfig.Skip} if testConfig != nil { skipConfigs = append(skipConfigs, testConfig.Skip) } if skip := testrunner.AnySkipConfig(skipConfigs...); skip != nil { logger.Warnf("skipping %s test for %s/%s: %s (details: %s)", TestType, r.testFolder.Package, r.testFolder.DataStream, skip.Reason, skip.Link) return result.WithSkip(skip) } pkgManifest, err := packages.ReadPackageManifestFromPackageRoot(r.packageRootPath) if err != nil { return result.WithError(fmt.Errorf("failed to read manifest: %w", err)) } // join together results from verifyStreamConfig and verifySampleEvent return append(r.verifyStreamConfig(ctx, r.packageRootPath), r.verifySampleEvent(pkgManifest)...), nil } func (r tester) verifyStreamConfig(ctx context.Context, packageRootPath string) []testrunner.TestResult { resultComposer := testrunner.NewResultComposer(testrunner.TestResult{ Name: "Verify benchmark config", TestType: TestType, Package: r.testFolder.Package, DataStream: r.testFolder.DataStream, }) withOpts := []stream.OptionFunc{ stream.WithPackageRootPath(packageRootPath), } ctx, stop := signal.Enable(ctx, logger.Info) defer stop() hasBenchmark, err := stream.StaticValidation(ctx, stream.NewOptions(withOpts...), r.testFolder.DataStream) if err != nil { results, _ := resultComposer.WithError(err) return results } if !hasBenchmark { return []testrunner.TestResult{} } results, _ := resultComposer.WithSuccess() return results } func (r tester) verifySampleEvent(pkgManifest *packages.PackageManifest) []testrunner.TestResult { resultComposer := testrunner.NewResultComposer(testrunner.TestResult{ Name: "Verify " + sampleEventJSON, TestType: TestType, Package: r.testFolder.Package, DataStream: r.testFolder.DataStream, }) sampleEventPath, found, err := r.getSampleEventPath() if err != nil { results, _ := resultComposer.WithError(err) return results } if !found { // Nothing to do. return []testrunner.TestResult{} } if r.withCoverage { coverage, err := testrunner.GenerateBaseFileCoverageReport(resultComposer.CoveragePackageName(), sampleEventPath, r.coverageType, true) if err != nil { results, _ := resultComposer.WithErrorf("coverage report generation failed: %w", err) return results } resultComposer = resultComposer.WithCoverage(coverage) } expectedDatasets, err := r.getExpectedDatasets(pkgManifest) if err != nil { results, _ := resultComposer.WithError(err) return results } fieldsValidator, err := fields.CreateValidatorForDirectory(filepath.Dir(sampleEventPath), fields.WithSpecVersion(pkgManifest.SpecVersion), fields.WithDefaultNumericConversion(), fields.WithExpectedDatasets(expectedDatasets), fields.WithEnabledImportAllECSSChema(true), ) if err != nil { results, _ := resultComposer.WithError(fmt.Errorf("creating fields validator for data stream failed: %w", err)) return results } content, err := os.ReadFile(sampleEventPath) if err != nil { results, _ := resultComposer.WithError(fmt.Errorf("can't read file: %w", err)) 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.Unique().Error(), }) return results } results, _ := resultComposer.WithSuccess() return results } func (r tester) getSampleEventPath() (string, bool, error) { var sampleEventPath string if r.testFolder.DataStream != "" { sampleEventPath = filepath.Join( r.packageRootPath, "data_stream", r.testFolder.DataStream, sampleEventJSON) } else { sampleEventPath = filepath.Join(r.packageRootPath, sampleEventJSON) } _, err := os.Stat(sampleEventPath) if errors.Is(err, os.ErrNotExist) { return "", false, nil } if err != nil { return "", false, fmt.Errorf("stat file failed: %w", err) } return sampleEventPath, true, nil } func (r tester) getExpectedDatasets(pkgManifest *packages.PackageManifest) ([]string, error) { dsName := r.testFolder.DataStream if dsName == "" { // TODO: This should return the package name plus the policy name, but we don't know // what policy created this event, so we cannot reliably know it here. Skip the check // by now. return nil, nil } dataStreamManifest, err := packages.ReadDataStreamManifestFromPackageRoot(r.packageRootPath, dsName) if err != nil { return nil, fmt.Errorf("failed to read data stream manifest: %w", err) } if ds := dataStreamManifest.Dataset; ds != "" { return []string{ds}, nil } return []string{pkgManifest.Name + "." + dsName}, nil } func (r tester) TearDown(ctx context.Context) error { return nil // it's a static test runner, no state is stored }