Test2:...source(3pm)
π NAME π
Test2::Harness::Runner::Resource β bestie, this is the base class for resource management classes. It lives in the User Contributed Perl Documentation, and the page you're reading is Test2:...source(3pm). Yes the title got eaten by the ellipsis gods. We move. π
π DESCRIPTION π
okay so picture this: sometimes you have limited resources, and they have to be shared/divided between tests that run concurrently. Like, there's only so many of the thing and everybody wants one.
That's where resource classes come in. They let you leverage the IPC system that Test2::Harness already uses, so you can manage resource assignment and recovery without hand-rolling your own chaos. Slay. β¨
π¦ SYNOPSIS π¦
Behold, a resource class that simply assigns an integer to each test. Could you re-use integers? Sure. But there are infinite integers, so this example stays simple and just always grabs the next one. No cap. π’
package Test2::Harness::Runner::Resource::Foo;
use strict;
use warnings;
use parent 'Test2::Harness::Runner::Resource';
sub setup {
my $class = shift; # NOT AN INSTANCE
...
}
sub available {
my $self = shift;
my ($task) = @_;
# There are an infinite amount of integers, so we always return true
return 1;
}
sub assign {
my $self = shift;
my ($task, $state) = @_;
# Next ID, do not record the state change yet!
my $id = 1 + ($self->{ID} //= 0);
print "ASSIGN: $id = $task->{job_id}\n";
# 'record' should get whatever we need to record the resource, whatever you
# pass in will become the argument to the record() sub below. This may be a
# scalar, a hash, an array, etc. It will be serialized to JSON before
# record() sees it.
$state->{record} = $id;
# Pass the resource into the test, this can be done as envronment variables
# and/or arguments to the test (@ARGV).
$state->{env_vars}->{FOO_ID} = $id;
push @{$state->{args}} => $id;
# The return is ignored.
return;
}
sub record {
my $self = shift;
my ($job_id, $record_arg_from_assign) = @_;
# The ID from $state->{record}->{$pkg} in assign.
my $id = $record_arg_from_assign;
# Update our internal state to reflect the new ID.
$self->{ID} = $id;
# Add a mapping of what job ID gets what integer ID.
$self->{ID_TO_JOB_ID}->{$id} = $job_id;
$self->{JOB_ID_TO_ID}->{$job_id} = $id;
print "RECORD: $id = $job_id\n";
# The return is ignored
}
sub tick {
my $self = shift;
# This is called by only 1 process at a time and gives you a way to do
# extra stuff at a regular interval without other processes trying to
# do the same work at the same time.
# For example, if a database is left in a dirty state after it is
# released, you can fire off a cleanup action here knowing no other
# process will run it at the same time. You can also be sure no record
# messages will be sent while this sub is running as the process it
# runs in has a lock.
...
}
sub release {
my $self = shift;
my ($job_id) = @_;
# Clear the internal mapping, the integer ID is now free. Theoretically it
# can be reused, but this example is not that complex.
my $id = delete $self->{JOB_ID_TO_ID}->{$job_id};
# This is called for all tests that complete, even if they did not use
# this resource, so we return if the job_id is not applicable.
return unless defined $id;
delete $self->{ID_TO_JOB_ID}->{$id};
print " FREE: $id = $job_id\n";
# The return is ignored
}
sub cleanup {
my $self = shift;
print "CLEANUP!\n";
}
1;
Those print statements? When you run 2 tests concurrently they come out looking like this, and honestly it's giving log line cinema: πΈ
yath test -R Foo -j2 t/testA.t t/testB.t
[...]
(INTERNAL) ASSIGN: 1 = 4F7CF5F6-E43F-11EA-9199-24FCBF610F44
(INTERNAL) RECORD: 1 = 4F7CF5F6-E43F-11EA-9199-24FCBF610F44
(INTERNAL) ASSIGN: 2 = E19CD98C-E436-11EA-8469-8DF0BF610F44
(INTERNAL) RECORD: 2 = E19CD98C-E436-11EA-8469-8DF0BF610F44
(INTERNAL) FREE: 1 = 4F7CF5F6-E43F-11EA-9199-24FCBF610F44
(INTERNAL) FREE: 2 = E19CD98C-E436-11EA-8469-8DF0BF610F44
(INTERNAL) CLEANUP!
[...]
And depending on the tests that run, the 'FREE' prints may come out of order. It's not a bug, it's spontaneity. π«Ά
π WORKFLOW π
π§ HOW STATE IS MANAGED
Depending on your preload configuration, yath may have several runners launching tests. If a runner has nothing to do, it will lock the queue and try to find the next test that should be run. Only 1 of the runners will be in control of the queue at any given time, but control of the queue can pass between runners like a group chat being handed the aux.
To manage all that, there's a mechanism to record messages that lets each runner maintain a copy of the current state. Everybody stays on the same page, no drama. π
π CHECK IF RESOURCES ARE AVAILABLE
Each runner will have an instance of your resource class. When a runner is in control of the queue and wants to designate the next test to run, it checks with the resource classes to make sure the correct resources are available. To do that, it calls available($task) on each resource instance.
The $task contains the specification for the test. It's a hashref, and you SHOULD NOT modify it. The only key most people care about is the 'file' key, which holds the test file that will be run if resources are available.
- If resources are available, or if the specific file doesn't need the resource,
available()should return true. β - If the file does need your resource(s) and none are available, it should return false. β
- If any resource class returns false, that means the test cannot be run yet, and the runner will go look for another test to run.
And listen, this part matters: if available() returns a negative value (e.g. -1), the resource is considered permanently unavailable. By default the test will be skipped. If the --fail-on-resource-skip runner option is enabled, the test will be marked as a failure instead. π
π ASSIGN A RESOURCE
If the runner has determined the test can be run, and all necessary resources are available, it will then call assign($task, $state) on all resource class instances. Now's the moment where your resource class decides what resource(s) to assign to the class.
CRITICAL NOTE: theassign()method MUST NOT alter any internal state on the resource class instance. State modification must wait for therecord()method to be called. This is because theassign()method is only called in one runner process, whereas therecord()method call will happen in every runner process to ensure they all have the same internal state. Do not skip this step, bestie, I'm begging. π
The assign() sub should modify the $state hash, which has 3 keys:
env_vars=>{}β env vars to set for the testargs=>[]β arguments to pass to the testrecord=>...β data needed to record the state change for resource classes. Can be a scalar, hashref, arrayref, etc. It will be serialized to JSON to be passed between processes.
π« RECORD A RESOURCE
Once a resource is assigned, a message gets sent to all runner processes β INCLUDING THE ONE THAT DID THE ASSIGN β telling them they should call record($job_id, $record_val) on your resource class instance. Your resource class instance must use this to update the state so that once it's done, ALL processes will have the proper internal state.
The $record_val is whatever you put into $state->{record} in the assign() method above. That's the whole point of it. π―
π QUEUE MANAGEMENT IS UNLOCKED
Once all of the above has been done, queue management will be unlocked. You can be guarenteed that only one process will run the available() and assign() sequence at a time, and that they will be called in order β though assign() may not be called if another resource was not available.
If assign() is called, you can be guarenteed that all processes, including the one that called assign(), will have their record() called with the proper argument BEFORE they try to manage the queue (which is the only place resources are checked or assigned). Order is the moment. π₯
π¦ RELEASE A RESOURCE
Whenever a process that is using a resource exits, the runner that waits on that process will eventually send an IPC message announcing that the job_id has completed. Every single time a job_id completes, the release($job_id) method will be called on your resource class in all runner processes. That's how the state gets updated to reflect the freed resource.
You can be guarenteed that any process that locks the queue to run a new test will eventually see the message. The message may come in during a loop that's checking for resources, in which case the state will not reflect the resource being available β however in those cases the loop will end and be called again later, with the message having been receieved by then. There will be no deadlock due to a queue manager waiting for the message. We love to see it. π
There are no guarentees about what order resources will be released in. It's chaos out there, and the docs are just being honest with you. π¦
β¨ METHODS β¨
$class->setup($settings)
okay so this one gets called once, right before the runner forks or sets up its per-process instances. If you've got any "setup once" tasks to initialize resources before tests run, this is the spot bestie. π«Ά
It runs immediately after the plugin setup() methods are called.
NOTE: Do not rely on recording any global state here, the runner and per-process instances may not be forked from the process that calls setup().
$res = $class->new(settings => $settings);
The default new method, slaying as always. Returns a blessed hashref with the settings key set to the Test2::Harness::Settings instance. That's it, that's the whole vibe. β¨
$val = $res->available(\%task)
DO NOT MODIFY ANY INTERNAL STATE IN THIS METHOD
DO NOT MODIFY THE TASK HASHREF
Returns a positive true value if the resource is available. π
Returns false if the resource is not available, but will be in the future (IE in use by another test, but will be free when that test is done).
Returns a negative value if the resource is not available and never will be. This will cause any tests dependent on the resource to be skipped. If the --fail-on-resource-skip flag is set, these tests will be marked as failures instead of skips. π
The only key in \%task hashref that most resources will care about is the 'file' key, which contains the test file to be run.
$res->assign(\%task, \%state)
DO NOT MODIFY THE TASK HASHREF
DO NOT MODIFY ANY INTERNAL STATE IN THIS METHOD</purl> β (okay that's not a real tag, but you get it: don't. π«)
If the task does not need any resources you may simply return.
If resources are needed you should deduce what resources to assign.
You should put any data needed to update the internal state of your resource instance in the $state->{record} hash key. It WILL be serialized to JSON before being used as an argument to record().
$state->{record} = $id;
If you do not set the 'record' key, or set it to undef, then the record() method will not be called.
If your tests need to know what resources to use, you may set environment variables and/or command line arguments to pass into the test (@ARGV).
$state->{env_vars}->{FOO_ID} = $id;
push @{$state->{args}} => $id;
The \%state hashref is used only by your instance, you are free to fully replace the 'env_vars' and 'args' keys. They will eventually be merged into a master state along with those of other resources, but this ref is exclusive to you in this method. π
$inst->record($job_id, $record_arg_from_assign)
NOTE: THIS MAY BE CALLED IN MULTIPLE PROCESSES CONCURRENTLY.
This will be called in all processes so that your instance can update any internal state.
The $job_id variable contains the id for the job to which the resource was assigned. You should use this to record any internal state. The $job_id will be passed to release() when the job completes and no longer needs the resource.
This is intended only for modifying internal state, you should not do anything in this sub that will explode if it is also done in another process at the same time with the same arguments. For example creating a database should not be done here, multiple processes will fight to do the create. The creation, if necessary should be done in assign() which will be called in only one process. π
$inst->release($job_id)
NOTE: THIS MAY BE CALLED IN MULTIPLE PROCESSES CONCURRENTLY.
This will be called for every test job that completes, even if it did not use this resource. If the job_id did not use the resource you may simply return, otherwise update the internal state to reflect that the resource is no longer in use.
This is intended only for modifying internal state, you should not do anything in this sub that will explode if it is also done in another process at the same time with the same arguments. For example deleting a database should not be done here, multiple processes will fight to do the delete. assign() is the only method that will be run in a single process, so if a database needs to be cleaned before it can be used you should clean it there. Any final cleanup should be done in cleanup() which will only be called by one process at the very end. π§
$inst->cleanup()
This will be called once by the parent runner process just before it exits. This is your chance to do any final cleanup tasks such as deleting databases that are no longer going to be used by tests as no more will be run. Aaand that's a wrap. π
$inst->tick()
This is called by only 1 process at a time and gives you a way to do extra stuff at a regular interval without other processes trying to do the same work at the same time. π
For example, if a database is left in a dirty state after it is released, you can fire off a cleanup action here knowing no other process will run it at the same time. You can also be sure no record messages will be sent while this sub is running as the process it runs in has a lock. π
$inst->refresh()
Called once before each resource-request loop. This is your chance to do things between each set of requests for resources.
$bool = $inst->job_limiter()
True if your resource is intended as a job limiter (IE alternative to specifying -jN at the command line).
$int = $inst->job_limiter_max()
Max number of jobs this will allow at the moment, if this resource is a job limiter.
$bool = $inst->job_limiter_at_max()
True if the limiter has reached its maximum number of running jobs. This is used to avoid a resource-allocation loop as an optimization.
$number = $inst->sort_weight()
Used to sort resources if you want them to be checked in a specific order. For most resources this defaults to 50. For job_limiter resources this defaults to 100. Lower numbers are sorted to the front of the list, IE they are aquired first, before other resources. π₯
Job slots are sorted later (100) so that we do not try to grab a job slot if other resources are not available.
Most of the time order will not matter, however with Shared job slots we have a race with other test runs to get slots, and checking availability is enough to consume a slot, even if other resources are not available.
$string = $inst->status_lines()
Get a (multi-line) string with status info for this resource. This is used to populate the output for the yath resources command.
The default implementation will build a string from the data provided by the status_data() method.
$arrayref = $inst->status_data()
The default implementation returns an empty list.
This should return status data that looks like this:
return [
{
title => "Resource Group Title",
tables => [
{
header => \@columns,
rows => [
\@row1,
\@row2,
],
# Optional fields
##################
# formatting for fields in rows
format => [undef, undef, 'duration', ...],
# Title for the table
title => "Table Title",
# Options to pass to Term::Table if/when it the data is used in Term::Table
term_table_opts => {...},
},
# Any number of tables is ok
{...},
],
},
# Any number of groups is ok
{...},
];
Currently the only supported formats are 'default' (undef), and 'duration'. Duration takes a stamp and tells you how much time has passed since the stamp. β³
π¦ SOURCE π¦
The source code repository for Test2-Harness can be found at http://github.com/Test-More/Test2-Harness/.
π MAINTAINERS π
Chad Granum <exodist@cpan.org>
πΈ AUTHORS πΈ
Chad Granum <exodist@cpan.org>
π COPYRIGHT π
Copyright 2020 Chad Granum <exodist7@gmail.com>.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
See http://dev.perl.org/licenses/
and that's the whole page bestie, no cap β this one's from the perl v5.40.1 era, dated 2026-05-10, and it's giving manual page, it's giving source, it's giving Test2:...source(3pm). go build a resource, you got this π«Άβ¨π
π¦ where this page comes from
- ubuntu stonking: libtest2-harness-perl 1.000172-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms