internal/testrunner/runners/asset/tester.go (161 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 asset
import (
"context"
"errors"
"fmt"
"strings"
"github.com/elastic/elastic-package/internal/kibana"
"github.com/elastic/elastic-package/internal/logger"
"github.com/elastic/elastic-package/internal/packages"
"github.com/elastic/elastic-package/internal/resources"
"github.com/elastic/elastic-package/internal/testrunner"
)
type tester struct {
testFolder testrunner.TestFolder
packageRootPath string
kibanaClient *kibana.Client
resourcesManager *resources.Manager
globalTestConfig testrunner.GlobalRunnerTestConfig
withCoverage bool
coverageType string
}
type AssetTesterOptions struct {
TestFolder testrunner.TestFolder
PackageRootPath string
KibanaClient *kibana.Client
GlobalTestConfig testrunner.GlobalRunnerTestConfig
WithCoverage bool
CoverageType string
}
func NewAssetTester(options AssetTesterOptions) *tester {
tester := tester{
testFolder: options.TestFolder,
packageRootPath: options.PackageRootPath,
kibanaClient: options.KibanaClient,
globalTestConfig: options.GlobalTestConfig,
withCoverage: options.WithCoverage,
coverageType: options.CoverageType,
}
manager := resources.NewManager()
manager.RegisterProvider(resources.DefaultKibanaProviderName, &resources.KibanaProvider{Client: options.KibanaClient})
tester.resourcesManager = manager
return &tester
}
// Ensures that runner implements testrunner.Tester interface
var _ testrunner.Tester = new(tester)
// Type returns the type of test that can be run by this test runner.
func (r *tester) Type() testrunner.TestType {
return TestType
}
// String returns the name of the test runner.
func (r tester) String() string {
return "asset loading"
}
// 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
}
// Run runs the asset loading tests
func (r *tester) Run(ctx context.Context) ([]testrunner.TestResult, error) {
return r.run(ctx)
}
func (r *tester) resources(installedPackage bool) resources.Resources {
return resources.Resources{
&resources.FleetPackage{
RootPath: r.packageRootPath,
Absent: !installedPackage,
Force: installedPackage, // Force re-installation, in case there are code changes in the same package version.
},
}
}
func (r *tester) run(ctx context.Context) ([]testrunner.TestResult, error) {
result := testrunner.NewResultComposer(testrunner.TestResult{
TestType: TestType,
Package: r.testFolder.Package,
})
if r.kibanaClient == nil {
return result.WithError(errors.New("missing Kibana client"))
}
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)
}
logger.Debug("installing package...")
_, err = r.resourcesManager.ApplyCtx(ctx, r.resources(true))
if err != nil {
return result.WithError(fmt.Errorf("can't install the package: %w", err))
}
manifest, err := packages.ReadPackageManifestFromPackageRoot(r.packageRootPath)
if err != nil {
return result.WithError(fmt.Errorf("cannot read the package manifest from %s: %w", r.packageRootPath, err))
}
installedPackage, err := r.kibanaClient.GetPackage(ctx, manifest.Name)
if err != nil {
return result.WithError(fmt.Errorf("cannot get installed package %q: %w", manifest.Name, err))
}
installedAssets := installedPackage.Assets()
// No Elasticsearch asset is created when an Input package is installed through the API.
// This would require to create a Agent policy and add that input package to the Agent policy.
// As those input packages could have some required fields, it would also require to add
// configuration files as in system tests to fill those fields.
// In these tests, mainly it is required to test Kibana assets, therefore it is not added
// support for Elasticsearch assets in input packages.
// Related issue: https://github.com/elastic/elastic-package/issues/1623
expectedAssets, err := packages.LoadPackageAssets(r.packageRootPath)
if err != nil {
return result.WithError(fmt.Errorf("could not load expected package assets: %w", err))
}
results := make([]testrunner.TestResult, 0, len(expectedAssets))
for _, e := range expectedAssets {
rc := testrunner.NewResultComposer(testrunner.TestResult{
Name: fmt.Sprintf("%s %s is loaded", e.Type, e.ID),
Package: r.testFolder.Package,
DataStream: e.DataStream,
TestType: TestType,
})
var tr []testrunner.TestResult
if !findActualAsset(installedAssets, e) {
tr, _ = rc.WithError(testrunner.ErrTestCaseFailed{
Reason: "could not find expected asset",
Details: fmt.Sprintf("could not find %s asset \"%s\". Assets loaded:\n%s", e.Type, e.ID, formatAssetsAsString(installedAssets)),
})
} else {
tr, _ = rc.WithSuccess()
}
result := tr[0]
if r.withCoverage && e.SourcePath != "" {
result.Coverage, err = testrunner.GenerateBaseFileCoverageReport(rc.CoveragePackageName(), e.SourcePath, r.coverageType, true)
if err != nil {
tr, _ = rc.WithError(testrunner.ErrTestCaseFailed{
Reason: "could not generate test coverage",
Details: fmt.Sprintf("could not generate test coverage for asset in %s: %v", e.SourcePath, err),
})
result = tr[0]
}
}
results = append(results, result)
}
return results, nil
}
func (r *tester) TearDown(ctx context.Context) error {
// Avoid cancellations during cleanup.
cleanupCtx := context.WithoutCancel(ctx)
logger.Debug("removing package...")
_, err := r.resourcesManager.ApplyCtx(cleanupCtx, r.resources(false))
if err != nil {
return err
}
return nil
}
func findActualAsset(actualAssets []packages.Asset, expectedAsset packages.Asset) bool {
for _, a := range actualAssets {
if a.Type == expectedAsset.Type && a.ID == expectedAsset.ID {
return true
}
}
return false
}
func formatAssetsAsString(assets []packages.Asset) string {
var sb strings.Builder
for _, asset := range assets {
sb.WriteString(fmt.Sprintf("- %s\n", asset.String()))
}
return sb.String()
}