Skip to main content

Getting started

Yaml test list​

Go to the root of your project and create a file named testlist.smelt.yaml with the following content:

- name: say_hey
rule: raw_bash_build
rule_args:
cmds:
- echo "hey"

- name: say_bye
rule: raw_bash
rule_args:
cmds:
- echo "bye"
deps:
- say_hey

Execute this testlist by executing smelt execute testlist.smelt.yaml.

Each command generated in a test list is executed in the directory containing the test list.

Smelt works by converting test lists to a series of bash commands, that are then executed by the smelt runtime in sequence.

Procedural test lists​

Procedural testlists can be very useful, particularly for constrained random testing or directed testing sweeps (e.g. sweeping a test from low to high load).

Smelt supports procedural test generation using smelt's Python interface; a simple example:

procedural.py
# test name
from pysmelt.default_targets import raw_bash

for i in range(5):
raw_bash(name=f"my_test_{i}", cmds=[f'echo "howdy partner from test {i}"'])

Executing this file with smelt execute procedural.py

[11:55:09] Executed 5 commands, 5 commands passed
┏━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━┓
┃ Command Name ┃ Status ┃ Execution Time ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━┩
│ my_test_2 │ PASSED │ 0.01ms │
│ my_test_4 │ PASSED │ 0.01ms │
│ my_test_1 │ PASSED │ 0.01ms │
│ my_test_0 │ PASSED │ 0.01ms │
│ my_test_3 │ PASSED │ 0.01ms │
└──────────────┴────────┴────────────────┘

Inspecting test results, manually​

Each command that is executed in smelt creates a directory at path ${GIT_ROOT}/smelt-out/${COMMAND_NAME}, that will hold the following files

  • command.sh: The bash script that is executed to execute this command
  • command.out: the combined stderr and stdout of this command.

The command will often generate outputs in this directory, but there is no enforcement of this policy.

Inspecting test results, programmatically​

After executing a testlist, smelt will produce an invocation object at path smelt-out/invocation.bin that includes information on the commands that were just executed.

Smelt's Python interface has convenient methods to interact with this invocation object -- the object is defined as a protobuf message, defined in the smelt-data crate.

For example, to inspect stdout from the my_test_5 from the previous step, we could execute the following Python script:

from pysmelt.interfaces.analysis import IQL
iql = IQL.from_previous()
log_content = iql.get_log_content()
if log_content:
for line in log_content.split('\n'):
print(line)

Running commands under a Docker container​

You can run each command in a testlist under a Docker container with smelt execute-docker.

Non-local dependencies​

You can declare dependencies to other testlists in your project, relative to the smelt root. An example of this can be seen in yves

# file exists at ${GIT_ROOT}/download_zig.smelt.yaml
- name: "cpp_compiler"
rule: download_zig
# file exists at ${GIT_ROOT}/profilers/buildprof.smelt.yaml
- name: profiler
rule: local_profiler
rule_args:
compiler_download: //download_zig.smelt.yaml:cpp_compiler
mac_sources:
- mac_profiler.c
- cJSON.c
linux_sources:
- linux_profiler.c
- cJSON.c

Validating testlists​

To validate that a testlist is well formed, without executing the tests, execute

smelt validate path/to/testlist.smelt.yaml

This will print out all the commands present in the test list.

Automatic rerun failing commands​

Smelt supports automatic re-run of failing commands, if the Target generates a rerun command.

A command fails if it returns a nonzero exit code.

For example, executing this test list

example.smelt.yaml
- name: cmark
rule: raw_bash
rule_args:
cmds:
- echo "normal"
- exit 1
debug_cmds:
- echo "we are re-running now . crazy"

will execute two commands -- cmark, and cmark@rerun, after the previous command fails.

For more complex rerun semantics -- e.g., rebuilding a binary with debug flags, and then running a new test with that binary, see details in the internals page.

Creating new targets​

Every example prior to this has used default targets packaged with smelt, specifically raw_bash and raw_bash_build.

End users can define their own smelt targets in their own repo by defining classes that inherit from the Target class in your ${GIT_ROOT}/smelt_rules directory.

# create this file in smelt_rules/seeded.py

from dataclasses import dataclass
from pysmelt.interfaces import Target
from typing import List

@dataclass
class seeded_simulator_test(Target):
seed: int
simulator_bin: str

def gen_script(self) -> List[str]:
return [f"{self.simulator_bin} --seed {self.seed}"]

def gen_rerun_script(self) -> Optional[List[str]]:
return [f"{self.simulator_bin} --seed {self.seed} --verbose"]

Now you can use this new rule in test lists

example.smelt.yaml
- name: seed_1000
rule: seeded_simulator_test
rule_args:
seed: 1000
simulator_bin: some_simulator


Then execute this testlist with with smelt execute example.smelt.yaml

To use the new target in a procedural test list, create:

# created at path new_rule.py

from pysmelt.generators.procedural import init_local_rules
init_local_rules()
from seeded import seeded_simulator_test

simulator_bin = "some_simulator"
for i in range(50):
seeded_simulator_test(name=f"seed_{i}, seed=i, simulator_bin=simulator_bin)

and call smelt execute new_rule.py

Using smelt as a library​

Smelt can be used as a Python library; here's an example of reading and executing a test list:

from pysmelt.pygraph import PyGraph, create_graph, create_graph_with_docker

some_testlist = "testlist.smelt.yaml"
graph = create_graph(some_testlist)
graph.run_all_commands()