Security News
Maven Central Adds Sigstore Signature Validation
Maven Central now validates Sigstore signatures, making it easier for developers to verify the provenance of Java packages.
Timid is a command line tool for running tests. It differs from Python tools like tox in that it is not limited to Python. It uses a YAML file to describe how to build the environment to run the test in.
Timid is intended to provide a very flexible test description language. It provides functionality for setting up various aspects of the test environment, as well as for actually invoking the test command. While not too dissimilar from a Python tool like tox, Timid does not make any assumptions about what that environment should look like; in particular, it does not create a virtual environment unless the test description includes the appropriate commands to do so. This makes it suitable for running any set of tests.
Another aspect of Timid is the ability to reference any subset of test steps from other files. This enables easy reuse for complicated test descriptions, and even means a library of test description fragments may be easily established and used. Timid also allows the working directory to be directly set from the command line, allowing the test descriptions to be separated from the actual code to test. Finally, Timid is extremely extensible; new test step actions and modifiers may be created by including a class in the appropriate entrypoint namespaces ("timid.actions" and "timid.modifiers", respectively), and extensions (namespace "timid.extensions") may also be created that can perform specific tasks under control of the command line--for instance, an extension could allow a Timid test to run on a Github pull request, setting test status information using the Github status API.
Test descriptions are YAML files consisting of a list of dictionaries, where each dictionary describes a step in the testing process. Each step consists of one action and zero or more modifiers which alter the action in some way. A step may also have a name, which is used to identify the step in the output, and a description. The action and the modifiers are identified by the keys of the step dictionary; the values of those keys identify the options for that action or modifier. The options may be any legal YAML entity, such as a string, integer, boolean, list, or a dictionary; the documentation for each of the actions and modifiers will describe what that action or modifier expects.
The test description could also be a smaller component of a YAML file containing a dictionary; each value of this dictionary must be a list of dictionaries, as described above. This could be used to describe several different but related tests for a single project (e.g., style tests, unit tests, functional tests, and integration tests), or it could be used to provide a library of test steps that can be included using the "include" action.
Many actions and modifiers allow Jinja2-style templates to be specified for values, which enhances reusability of test description components. Jinja2-style expressions can also be used; an example would be the "when" modifier, which provides simple conditional control of an action. Template variables can be set on the command line, read from a YAML file, or set up directly in the test description.
Timid provides a way to mark both template variables and environment
variables as being "sensitive". This is to allow security-sensitive
data, such as usernames and passwords, to be used, while ensuring that
that sensitive data is scrubbed from any verbose or debugging output
from Timid itself. For template variables, this can only be done from
the test description file, but environment variables can also be
marked sensitive by listing them in the TIMID_SENSITIVE
environment variable, separated by your system's path separator
character. (On UNIX and Linux systems, this character would be the
":" character.) The TIMID_SENSITIVE
environment variable will
also be present in the environment of any subordinate processes,
updated with any additional environment variables marked as sensitive
by the test description; this can be used by test scripts to omit
sensitive information from the environment in debugging output.
As mentioned previously, Timid uses Python entrypoints for simple
extensibility. Each action or modifier in a test description is
looked up for in the "timid.actions" or "timid.modifiers" namespaces,
respectively. These entrypoints must map to subclasses of
timid.Action
or timid.Modifier
, as appropriate. Timid also
provides extensions, which allow extending the actual command line
interface and other per-step behavior; these are listed in the
"timid.extensions" interface, and the entrypoints must map to
subclasses of timid.Extension
.
Actions perform the actual test step. Creating a new action is a
matter of extending timid.Action
. In the new action class, the
schema
class attribute must be set to a JSONSchema description of
the expected configuration; and the __call__()
method, taking as
its sole argument a context object, must be defined to implement the
actual action; it should return an instance of timid.StepResult
.
The timid.Action
class declares a __init__()
method taking
four arguments (a context object, the name of the action (the key read
from the test description), the configuration for the action (the
value for that key), and a step address object); it validates the
configuration, then stores the last three arguments in the name
,
config
, and step_addr
attributes, respectively. (The context
object should not be stored; it will be passed in to the
__call__()
method.)
There are two types of actions. By default, all timid.Action
subclasses are instantiated while reading the test description, then
their __call__()
methods are invoked in order during the actual
test run--or not invoked at all, if a syntax check is being
performed. However, it is possible to create a "step action", an
action invoked immediately after it is read from the test description;
this is used, for instance, to implement the "include" step, which
reads steps from another file and inserts them in place of the
"include" step. These are implemented by setting the step_action
class attribute to True
and having __call__()
return a list of
timid.Step
objects, instead of a timid.StepResult
object.
Modifiers modify a step in some fashion, such as by running the step
in a loop or applying a conditional prior to invoking the step.
Creating a new action is a matter of extending timid.Modifier
.
Like actions, the new subclass must have a schema
class attribute
set to a JSONSchema description of the expected configuration, and a
__init__()
method identical to that for timid.Action
is also
implemented; however, modifiers do not have a __call__()
method,
and the class attribute priority
must be set to an integer value.
The priority
attribute controls the order in which modifiers are
applied while running a step, with lower values invoked before higher
values.
A modifier actually consists of a set of hook functions. The
timid.Modifier
superclass contains default implementations of
these hook functions, so a developer need only override the ones
needed to implement the modifier.
The first hook is the action_conf()
hook, which takes 5 arguments:
a context object, the class implementing the modified action, the name
of the action (key read from the test description), the configuration
for the action (the __init__()
method receives the configuration
for the modifier), and a step address object. The hook function
must return the configuration that should be passed to the action
class, giving the modifier the opportunity to alter the configuration.
The remaining two hooks are the pre_call()
and post_call()
methods, which are invoked prior to and after calling the action's
__call__()
method, respectively. The pre_call()
method can
return a timid.StepResult
object, which aborts further processing
(including the call to the action) and proceeds with invoking any
post_call()
methods. The post_call()
method receives the
timid.StepResult
object and can modify it or even replace it
entirely, by returning a different object. The pre_call()
method
takes 4 arguments: a context object, a "pre_mod" list, a "post_mod"
list, and the instance of the timid.Action
subclass. The
post_call()
method gets 5 arguments: a context object, the result
of the call (an instance of timid.StepResult
), the instance of the
timid.Action
subclass, a "post_mod" list, and a "pre_mod" list.
The "pre_mod" and "post_mod" lists are lists of timid.Modifier
instances that have lower priority and higher priority, respectively.
It should also be noted that post_call()
is called in the inverse
order of pre_call()
.
The context object passed to the actions and modifier methods provides
several services throughout Timid. The verbose
attribute contains
an integer value controlling the verbosity of Timid's output (0 means
no output at all), and debug
is a boolean indicating whether
debugging is enabled. The variables
attribute contains a
dictionary of template variables, and environment
contains the
environment variables. (The environment dictionary-like object also
allows control of the current working directory, by setting its
cwd
attribute, and its call()
method should be used to invoke
external programs.)
Timid provides an interface to Jinja2, and two utility methods on the
context object facilitate this: the template()
method takes a
string and returns a callable of one argument that will render the
template, and expression()
works similarly for Jinja2
expressions. (It is safe to pass objects other than strings to these
two methods as well; the result will still be a callable of one
argument, but no template expansion will be performed.) The context
object should be passed to the callable returned by template()
and
expression()
.
The usual way to use the template()
and expression()
methods
is to override the __init__()
method of the timid.Action
or
timid.Modifier
subclass; the method should invoke the superclass's
version of __init__()
(using a super()
expression), and would
then process the configuration, saving the callables produced by
calling template()
and expression()
. Then, where the values
are used in the action's __call__()
or the modifier's hook
methods, simply pass the context to the callable and use the result as
the actual value to use.
To aid debugging, each action or modifier has a step address object
associated with it. The address has three attributes: the filename
from which the step was read (fname
); the 0-based index of the
step within the file (idx
); and the key for the list containing
the steps (key
). (This latter attribute will be None
if the
file was a simple list of steps.) The object also has a
straightforward string representation which includes the filename,
key, and step index (1-based; that is, if idx
is 3, the string
will identify the step as step 4).
Extensions are the most powerful extensibility mechanism in Timid.
Creating one is a matter of extending timid.Extension
and
implementing the desired hook methods, similar to creating a new
modifier, except that a Timid extension must implement an
activate()
class method if it actually intends to do anything.
Additionally, a timid.Extension
subclass must set the priority
class attribute to a numerical value, just like a timid.Modifier
subclass; extension hook functions will be called in the order
dictated by the priorities.
The first hook method that an extension may implement is the
prepare()
method. This must be a class method, and will receive
as its sole argument an argparse.ArgumentParser
instance, which
the extension may use to declare new command line options. All
extensions will have their prepare()
method called during Timid
initialization.
Once the command line has been processed by
argparse.ArgumentParser
, each extension's activate()
method
will be called with a context object and an argparse.Namespace
containing the results of the command line processing. This method
must also be a class method, and must return either None
or an
instance of the timid.Extension
subclass; if it returns None
,
the extension is treated as inactive and no other hook methods will be
called.
The remaining hook methods are all instance methods, called on the
object returned by the activate()
method. The read_steps()
method is called with a context object and a list of timid.Step
instances; the extension may perform any in-place modifications to the
list of steps that are appropriate. The pre_step()
and
post_step()
methods are called before and after executing a step,
respectively; pre_step()
is called with a context object, the
timid.Step
instance, and the index of the step, and may return a
True
value to cause the step to be skipped. The post_step()
method is called with the same arguments, and a fourth argument, which
will be a timid.StepResult
object, which it may alter in place;
the return value of post_step()
is ignored. Note that
post_step()
is called in extension order, in contrast to the
post_call()
method of timid.Modifier
instances.
The final hook function is the finalize()
method, which is called
just before the command line tool exits. It is called with a context
object and the result, which will typically be None
for success,
or a text string indicating an error. (It could also be called with
an Exception
instance if an error occurred.) This method's return
value will replace the result.
The implementation of extensions explicitly ignores exceptions raised
by a given extension. This would make it difficult to debug a newly
developed extension, so Timid provides a debugging mechanism: the
TIMID_EXTENSION_DEBUG
environment variable may be set to an
integer value, with larger values resulting in more verbose debugging.
If TIMID_EXTENSION_DEBUG
is present in the environment with no
value, or with a non-integer value, the debugging level will be set to
1; a debugging level of 0 (or any negative value) is exactly the same
as if TIMID_EXTENSION_DEBUG
was not present in the environment at
all.
Note that this environment variable is checked directly from the
environment, unlike the TIMID_SENSITIVE
environment variable.
This means that the value used by extension debugging cannot be
altered by any instructions in the test description; only child
processes can be affected by such instructions. Even command line
environment variable manipulations are ignored for the purposes of
extension debugging. This design decision was made so that debugging
could be enabled before even calling the extension prepare()
method, which is called before any argument processing is done, and
thus prior to reading any test description files.
FAQs
Timid test runner
We found that timid demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Maven Central now validates Sigstore signatures, making it easier for developers to verify the provenance of Java packages.
Security News
CISOs are racing to adopt AI for cybersecurity, but hurdles in budgets and governance may leave some falling behind in the fight against cyber threats.
Research
Security News
Socket researchers uncovered a backdoored typosquat of BoltDB in the Go ecosystem, exploiting Go Module Proxy caching to persist undetected for years.