scenario

package
v0.3.24 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 4, 2023 License: Apache-2.0 Imports: 31 Imported by: 0

Documentation

Overview

Package scenario provides a high-level testing framework for running Bacalhau jobs in different configurations and making assertions against the results.

The unit of measure is the `Scenario` which decsribes a Bacalhau network, a job to be submitted to it, and a set of checks to exercise that the job was executed as expected.

Scenarios can be used in standalone way (see `pkg/test/executor/test_runner.go`) or using the provided `ScenarioRunner` can be used.

As well as executing jobs against real executors, a Scenario can instead use the NoopExecutor to implement a mocked out job. This makes is easier to test network features without needing to invent a real job.

Example (Basic)
package main

import (
	"testing"

	"github.com/bacalhau-project/bacalhau/pkg/model"
	"github.com/stretchr/testify/suite"
)

var basicScenario Scenario = Scenario{
	Inputs: ManyStores(
		StoredText("hello, world!", "/inputs"),
		StoredFile("../../../testdata/wasm/cat/main.wasm", "/job"),
	),
	Outputs: []model.StorageSpec{},
	Spec: model.Spec{
		Engine: model.EngineWasm,
		Wasm: model.JobSpecWasm{
			EntryPoint: "_start",
		},
	},
	ResultsChecker: FileEquals(model.DownloadFilenameStdout, "hello, world!\n"),
	JobCheckers:    WaitUntilSuccessful(1),
}

type ExampleTest struct {
	ScenarioRunner
}

func main() {
	// In a real example, use the testing.T passed to the TestXxx method.
	suite.Run(&testing.T{}, new(ExampleTest))
}

func (suite *ExampleTest) TestRun() {
	suite.RunScenario(basicScenario)
}
Output:

Example (Noop)
package main

import (
	"context"
	"strings"
	"testing"

	"github.com/bacalhau-project/bacalhau/pkg/executor"
	"github.com/bacalhau-project/bacalhau/pkg/executor/noop"
	"github.com/bacalhau-project/bacalhau/pkg/model"
	"github.com/stretchr/testify/suite"
)

var noopScenario Scenario = Scenario{
	Stack: &StackConfig{
		ExecutorConfig: noop.ExecutorConfig{
			ExternalHooks: noop.ExecutorConfigExternalHooks{
				JobHandler: func(ctx context.Context, job model.Job, resultsDir string) (*model.RunCommandResult, error) {
					return executor.WriteJobResults(resultsDir, strings.NewReader("hello, world!\n"), nil, 0, nil)
				},
			},
		},
	},
	Spec: model.Spec{
		Engine: model.EngineNoop,
		Wasm: model.JobSpecWasm{
			EntryPoint: "_start",
		},
	},
	ResultsChecker: FileEquals(model.DownloadFilenameStdout, "hello, world!\n"),
	JobCheckers:    WaitUntilSuccessful(1),
}

type NoopTest struct {
	ScenarioRunner
}

func main() {
	// In a real example, use the testing.T passed to the TestXxx method.
	suite.Run(&testing.T{}, new(NoopTest))
}

func (suite *NoopTest) TestRunNoop() {
	suite.RunScenario(noopScenario)
}
Output:

Index

Examples

Constants

This section is empty.

Variables

View Source
var AwkFile = Scenario{
	Inputs: StoredFile(
		"../../../testdata/awk_file.txt",
		simpleMountPath,
	),
	ResultsChecker: FileContains(
		model.DownloadFilenameStdout,
		"LISBON",
		501,
	),
	Spec: model.Spec{
		Engine: model.EngineDocker,
		Docker: model.JobSpecDocker{
			Image: "ubuntu:latest",
			Entrypoint: []string{
				"awk",
				"-F,",
				"{x=38.7077507-$3; y=-9.1365919-$4; if(x^2+y^2<0.3^2) print}",
				simpleMountPath,
			},
		},
	},
}
View Source
var CatFileToStdout = Scenario{
	Inputs: StoredText(
		helloWorld,
		simpleMountPath,
	),
	ResultsChecker: ManyChecks(
		FileEquals(model.DownloadFilenameStderr, ""),
		FileEquals(model.DownloadFilenameStdout, helloWorld),
	),
	Spec: model.Spec{
		Engine: model.EngineWasm,
		Wasm: model.JobSpecWasm{
			EntryPoint:  "_start",
			EntryModule: InlineData(cat.Program()),
			Parameters:  []string{simpleMountPath},
		},
	},
}
View Source
var CatFileToVolume = Scenario{
	Inputs: StoredText(
		catProgram,
		simpleMountPath,
	),
	ResultsChecker: FileEquals(
		"test/output_file.txt",
		catProgram,
	),
	Outputs: []model.StorageSpec{
		{
			Name: "test",
			Path: "/output_data",
		},
	},
	Spec: model.Spec{
		Engine: model.EngineDocker,
		Docker: model.JobSpecDocker{
			Image: "ubuntu:latest",
			Entrypoint: []string{
				"bash",
				simpleMountPath,
			},
		},
	},
}
View Source
var GrepFile = Scenario{
	Inputs: StoredFile(
		"../../../testdata/grep_file.txt",
		simpleMountPath,
	),
	ResultsChecker: FileContains(
		model.DownloadFilenameStdout,
		"kiwi is delicious",
		2,
	),
	Spec: model.Spec{
		Engine: model.EngineDocker,
		Docker: model.JobSpecDocker{
			Image: "ubuntu:latest",
			Entrypoint: []string{
				"grep",
				"kiwi",
				simpleMountPath,
			},
		},
	},
}
View Source
var SedFile = Scenario{
	Inputs: StoredFile(
		"../../../testdata/sed_file.txt",
		simpleMountPath,
	),
	ResultsChecker: FileContains(
		model.DownloadFilenameStdout,
		"LISBON",
		5,
	),
	Spec: model.Spec{
		Engine: model.EngineDocker,
		Docker: model.JobSpecDocker{
			Image: "ubuntu:latest",
			Entrypoint: []string{
				"sed",
				"-n",
				"/38.7[2-4]..,-9.1[3-7]../p",
				simpleMountPath,
			},
		},
	},
}
View Source
var WasmCsvTransform = Scenario{
	Inputs: StoredFile(
		"../../../testdata/wasm/csv/inputs",
		"/inputs",
	),
	ResultsChecker: FileContains(
		"outputs/parents-children.csv",
		"http://www.wikidata.org/entity/Q14949904,Tugela,http://www.wikidata.org/entity/Q1001792,Makybe Diva",
		269,
	),
	Spec: model.Spec{
		Engine: model.EngineWasm,
		Wasm: model.JobSpecWasm{
			EntryPoint:  "_start",
			EntryModule: InlineData(csv.Program()),
			Parameters: []string{
				"inputs/horses.csv",
				"outputs/parents-children.csv",
			},
		},
	},
	Outputs: []model.StorageSpec{
		{
			Name: "outputs",
			Path: "/outputs",
		},
	},
}
View Source
var WasmEnvVars = Scenario{
	ResultsChecker: FileContains(
		"stdout",
		"AWESOME=definitely\nTEST=yes\n",
		3,
	),
	Spec: model.Spec{
		Engine: model.EngineWasm,
		Wasm: model.JobSpecWasm{
			EntryPoint:  "_start",
			EntryModule: InlineData(env.Program()),
			EnvironmentVariables: map[string]string{
				"TEST":    "yes",
				"AWESOME": "definitely",
			},
		},
	},
}
View Source
var WasmExitCode = Scenario{
	ResultsChecker: FileEquals(
		model.DownloadFilenameExitCode,
		"5",
	),
	Spec: model.Spec{
		Engine: model.EngineWasm,
		Wasm: model.JobSpecWasm{
			EntryPoint:           "_start",
			EntryModule:          InlineData(exit_code.Program()),
			Parameters:           []string{},
			EnvironmentVariables: map[string]string{"EXIT_CODE": "5"},
		},
	},
}
View Source
var WasmHelloWorld = Scenario{
	ResultsChecker: FileEquals(
		model.DownloadFilenameStdout,
		"Hello, world!\n",
	),
	Spec: model.Spec{
		Engine: model.EngineWasm,
		Wasm: model.JobSpecWasm{
			EntryPoint:  "_start",
			EntryModule: InlineData(noop.Program()),
			Parameters:  []string{},
		},
	},
}

Functions

func GetAllScenarios

func GetAllScenarios() map[string]Scenario

func InlineData

func InlineData(data []byte) model.StorageSpec

InlineFile will store the file directly inline in the storage spec. Unlike the other storage set-ups, this function loads the file immediately. This makes it possible to store things deeper into the Spec object without the test system needing to know how to prepare them.

func WaitUntilSuccessful

func WaitUntilSuccessful(nodes int) []job.CheckStatesFunction

WaitUntilSuccessful returns a set of job.CheckStatesFunctions that will wait until the job they are checking reaches the Completed state on the passed number of nodes. The checks will fail if any job errors.

Types

type CheckResults

type CheckResults func(resultsDir string) error

A CheckResults is a function that will examine job output that has been written to storage and assert something about it. If the condition it is checking is false, it returns an error, else it returns nil.

func FileContains

func FileContains(
	outputFilePath string,
	expectedString string,
	expectedLines int,
) CheckResults

FileContains returns a CheckResults that asserts that the expected string is in the output file and that the file itself is of the correct size.

func FileEquals

func FileEquals(
	outputFilePath string,
	expectedString string,
) CheckResults

FileEquals returns a CheckResults that asserts that the expected string is exactly equal to the full contents of the output file.

func ManyChecks

func ManyChecks(checks ...CheckResults) CheckResults

ManyCheckes returns a CheckResults that runs the passed checkers and returns an error if any of them fail.

type CheckSubmitResponse

type CheckSubmitResponse func(job *model.Job, err error) error

A CheckSubmitResponse is a function that will examine and validate submitJob response. Useful when validating that a job should be rejected.

func SubmitJobErrorContains

func SubmitJobErrorContains(msg string) CheckSubmitResponse

func SubmitJobFail

func SubmitJobFail() CheckSubmitResponse

SubmitJobFail returns a CheckSubmitResponse that asserts an error was returned when submitting a job.

func SubmitJobSuccess

func SubmitJobSuccess() CheckSubmitResponse

SubmitJobSuccess returns a CheckSubmitResponse that asserts no error was returned when submitting a job.

type Scenario

type Scenario struct {
	// An optional set of configuration options that define the network of nodes
	// that the job will be run against. When unspecified, the Stack will
	// consist of one node with requestor and compute nodes set up according to
	// their default configuration, and without a Noop executor.
	Stack *StackConfig

	// Setup routines which define data available to the job.
	// If nil, no storage will be set up.
	Inputs SetupStorage

	// Output volumes that must be available to the job. If nil, no output
	// volumes will be attached to the job.
	Outputs []model.StorageSpec

	// The job specification
	Spec model.Spec

	// The job deal. If nil, concurrency will default to 1.
	Deal model.Deal

	// A function that will assert submitJob response is as expected.
	// if nil, will use SubmitJobSuccess by default.
	SubmitChecker CheckSubmitResponse

	// A function that will decide whether or not the job was successful. If
	// nil, no check will be performed on job outputs.
	ResultsChecker CheckResults

	// A set of checkers that will decide when the job has completed, and maybe
	// whether it was successful or not. If empty, the job will not be waited
	// for once it has been submitted.
	JobCheckers []job.CheckStatesFunction
}

A Scenario represents a repeatable test case of submitting a job against a Bacalhau network.

The Scenario defines:

  • the topology and configuration of network that is required
  • the job that will be submitted
  • the conditions for the job to be considered successful or not

Most of the fields in a Scenario are optional and sensible defaults will be used if they are not present. All that is really required is the Spec which details what job to run.

type ScenarioRunner

type ScenarioRunner struct {
	suite.Suite
	Ctx context.Context
}

The ScenarioRunner is an object that can run a Scenario.

It will spin up an appropriate Devstack for the Scenario, submit and wait for the job to complete, and then make assertions against the results of the job.

ScenarioRunner implements a number of testify/suite interfaces making it appropriate as the basis for a test suite. If a test suite composes itself from the ScenarioRunner then default set up and tear down methods that instrument and configure the test will be used. Test suites should not define their own set up or tear down routines.

func (*ScenarioRunner) RunScenario

func (s *ScenarioRunner) RunScenario(scenario Scenario) (resultsDir string)

RunScenario runs the Scenario.

Spin up a devstack, execute the job, check the results, and tear down the devstack.

func (*ScenarioRunner) SetupTest

func (s *ScenarioRunner) SetupTest()

type SetupStorage

type SetupStorage func(
	ctx context.Context,
	driverName model.StorageSourceType,
	ipfsClients ...ipfs.Client,
) ([]model.StorageSpec, error)

A SetupStorage is a function that return a model.StorageSpec representing some data that has been prepared for use by a job. It is the responsibility of the function to ensure that the data has been set up correctly.

func ManyStores

func ManyStores(stores ...SetupStorage) SetupStorage

ManyStores runs all of the passed setups and returns the model.StorageSpecs associated with all of them. If any of them fail, the error from the first to fail will be returned.

func PartialAdd

func PartialAdd(numberOfNodes int, store SetupStorage) SetupStorage

PartialAdd will only store data on a subset of the nodes that it is passed. So if there are 5 IPFS nodes configured and PartialAdd is defined with 2, only the first two nodes will have data loaded.

func StoredFile

func StoredFile(
	filePath string,
	mountPath string,
) SetupStorage

StoredFile will store the file at the passed path on an IPFS node, and return the file name and CID in the model.StorageSpec.

func StoredText

func StoredText(
	fileContents string,
	mountPath string,
) SetupStorage

StoredText will store the passed string as a file on an IPFS node, and return the file name and CID in the model.StorageSpec.

func URLDownload

func URLDownload(
	server *httptest.Server,
	urlPath string,
	mountPath string,
) SetupStorage

URLDownload will return a model.StorageSpec referencing a file on the passed HTTP test server.

type StackConfig

All the information that is needed to uniquely define a devstack.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL