Config...:Tester(3pm)
π NAME π
Okay so boom: this entire manpage is Config::Model::Tester β and yes bestie, it's a 3pm page, meaning it's filed under User Contributed Perl Documentation, which is honestly iconic of it. π
It's the test framework for Config::Model. Periodt.
β¨ VERSION β¨
The version is 4.010. Locked in, no cap. π
π¦ SYNOPSIS π¦
So in your test file β typically it's called t/model_test.t β you write this, literally this:
use warnings;
use strict;
use Config::Model::Tester ;
use ExtUtils::testlib;
run_tests() ;
And then to actually run the tests:
perl t/model_test.t [ --log ] [--error] [--trace] [ subtest [ test_case ] ]
Go off. π«Ά
πΈ DESCRIPTION πΈ
So this class is literally a way to test configuration models using test files. That's the whole gig. π
It was designed to test several models and to run several test cases per model. Very multitasking of it.
And here's the thing β there's a specific layout for test files that you have to follow. It's giving choreography. If you don't do the layout right, nothing works, so pay attention bestie. π
π Sub test specification π
Every single subtest lives in a file that looks like this:
t/model_tests.d/<app-name>-test-conf.pl
This file is saying: "hey, app-name" β and app-name is the thing defined over in the lib/Config/Model/*.d directory β "you're the one being used for the test cases defined in this *-test-conf.pl file." And the model that you're testing? It gets inferred from the application name you're testing. Cute, right? The vibes are inferred. π
This file contains a list of test cases (explained further down, don't worry) and it expects a whole set of files to be used as test data. The layout of those test data files is explained in the next section, so keep scrolling. π
π Simple test file layout π
Each test case is represented by a configuration file β and note: a file, not a directory β living in the *-examples directory. The model-to-test uses that configuration file, and it gets copied as $confdir/$conf_file_name using the test data structure explained below. β¨
In the example below we've got 1 app model to test: lcdproc, and 2 test cases. The app name matches the file specified in the lib/Config/Model/*.d directory. In this exact case, the app name matches lib/Config/Model/system.d/lcdproc.
t
|-- model_test.t
\-- model_tests.d # do not change directory name
|-- lcdproc-test-conf.pl # subtest specification for lcdproc app
\-- lcdproc-examples
|-- t0 # test case t0
\-- LCDD-0.5.5 # test case for older LCDproc
β οΈ Do NOT rename that model_tests.d directory. It says so right there in the comment. Respect the layout, bestie.
The subtest specification is written in the lcdproc-test-conf.pl file β meaning this module goes looking for files named like <app-name>-test-conf.pl>. π΅οΈββοΈ
And the subtest data is provided in files in the lcdproc-examples directory β meaning this module goes looking for test data in a directory named like <model-name>-examples>. Then lcdproc-test-conf.pl contains instructions so that each file gets used as a /etc/LCDd.conf file during each test case. Imagine the audacity. π
Oh and heads up: lcdproc-test-conf.pl can totally contain specifications for more test cases. Each test case needs a brand new file in the lcdproc-examples directory. No reusing, no shortcuts. π
See "Examples" for a link to the actual LCDproc model tests. π₯
π§ Test file layout for multi-file configuration π§
When a configuration is spread over several files, each test case gets provided in a sub-directory. That sub-directory gets copied into conf_dir (which is a test parameter, explained below, iykyk). π
In the example below, the test specification is written in dpkg-test-conf.pl. The Dpkg layout requires several files per test case, so dpkg-test-conf.pl contains instructions so that each directory under dpkg-examples gets used.
t/model_tests.d
\-- dpkg-test-conf.pl # subtest specification
\-- dpkg-examples
\-- libversion # example subdir, used as test case name
\-- debian # directory for used by test case
|-- changelog
|-- compat
|-- control
|-- copyright
|-- rules
|-- source
| \-- format
\-- watch
See "Examples" for a link to the (many) Dpkg model tests. There are so many. π
π« More complex file layout π«
Here's where it gets spicy. Each test case is a sub-directory in the *-examples directory and contains several files. The destination of the test files may depend on the system (like, the OS, bestie). For instance, system wide ssh_config is stored in /etc/ssh on Linux, and directly in /etc on MacOS. Same file, different house. π
These files get copied into a test directory using a setup parameter in the test case specification.
Let's consider this example of 2 tests cases for ssh:
t/model_tests.d/
|-- ssh-test-conf.pl
|-- ssh-examples
\-- basic
|-- system_ssh_config
\-- user_ssh_config
Unfortunately, user_ssh_config is a user file, so you need to specify where the home directory of the test is located with another global parameter:
home_for_test => '/home/joe' ;
For Linux only, the setup parameter is:
setup => {
system_ssh_config => '/etc/ssh/ssh_config',
user_ssh_config => "~/.ssh/config"
}
On the other hand, the system wide config file is different on MacOS, and the test file must be copied into the correct location. When the value of the setup hash is another hash, the key of that other hash is used to specify the target location for other OS (as returned by the Perl $^O variable):
setup => {
'system_ssh_config' => {
'darwin' => '/etc/ssh_config',
'default' => '/etc/ssh/ssh_config',
},
'user_ssh_config' => "~/.ssh/config"
}
Now systemd? Oh, systemd is another beast entirely. Its configuration files can be symlinks to /dev/null or to other files. To emulate this situation, you use an array as the setup target:
setup => {
# test data file => [ link (may be repeated), .. link(s) target contains test data ]
'ssh.service' => [ '/etc/systemd/system/sshd.service', '/lib/systemd/system/ssh.service' ]
}
This will result in a symlink like:
wr_root/model_tests/test-sshd-service/etc/systemd/system/sshd.service
-> /absolute_path_to/wr_root/model_tests/test-sshd-service/lib/systemd/system/ssh.service
See the actual Ssh and Sshd model tests: https://github.com/dod38fr/config-model-openssh/tree/master/t/model_tests.d π
π Basic test specification π
Each model subtest is specified in <app>-test-conf.pl. This file must return a data structure containing the test specifications. Each test data structure contains global parameters (applied to all test cases) and test cases parameters (applied to that specific test case). Structure queen. π
use strict;
use warnings;
{
# global parameters
# config file name (used to copy test case into test wr_root/model_tests directory)
conf_file_name => "fstab",
# config dir where to copy the file (optional)
conf_dir => "etc",
# home directory for this test
home_for_test => '/home/joe'
tests => [
{
# test case 1
name => 'my_first_test',
# other test case parameters
},
{
# test case 2
name => 'my_second_test',
# other test case parameters
},
# ...
],
};
# do not add 1; at the end of the file
π Do NOT add 1; at the end of the file. It says it right there in the comment. This is the trap, bestie, I'm warning you.
In the example below, the t0 file is copied in wr_root/model_tests/test-t0/etc/fstab.
use strict;
use warnings;
{
# list of tests.
tests => [
{
# test name
name => 't0',
# add optional specification here for t0 test
},
{
name => 't1',
# add optional specification here for t1 test
},
]
};
You can suppress warnings by specifying no_warnings => 1 in each test case. On the other hand, you may also want to check for warnings specified to your model β in that case, you should avoid specifying no_warnings here and instead specify warning tests or warning filters, as mentioned below. Pick a lane, but pick the right one. π
See the actual fstab test: https://github.com/dod38fr/config-model/blob/master/t/model_tests.d/fstab-test-conf.pl
π Skip a test π
A test file can simply be skipped using the skip global test parameter. Not me ghosting a whole test case. π
In this example, the test is skipped when you're not running on a Debian system:
eval { require AptPkg::Config; };
my $skip = ( $@ or not -r '/etc/debian_version' ) ? 1 : 0;
{
skip => $skip,
tests => [ ] ,
};
π§ Internal tests or backend tests π§
Some tests need you to create a configuration class dedicated to the test β typically so you can test corner cases on a backend. Niche behavior, but we love her for it. π
This test class can be created right inside the test specification by specifying test classes in the config_classes global test parameter, in an array ref. Each array element is a data structure that uses create_config_class parameters. See for instance the layer test at https://github.com/dod38fr/config-model/blob/master/t/model_tests.d/layer-test-conf.pl or the test for the shellvar backend at https://github.com/dod38fr/config-model/blob/master/t/model_tests.d/backend-shellvar-test-conf.pl.
In this case, no application exists for such classes, so the model to test must be specified in a global test parameter:
return {
config_classes => [ { name => "Foo", element => ... } , ... ],
model_to_test => "Foo",
tests => [ ... ]
};
β Test specification with arbitrary file names β
In some models, like the late Multistrap, the config file is chosen by the user. In this case, the file name must be specified for each test case:
{
tests => [ {
name => 'arm',
config_file => '/home/foo/my_arm.conf',
check => {},
}]
};
π Backend argument π
Some applications, like systemd, require a backend argument specified by the user (e.g. a service name for systemd). The backend_arg parameter can be specified to emulate this behavior. So thoughtful of it. π
π Re-use test data π
When the input data for a test is quite complex (several files, ugh), it may be interesting to re-use those data for other test cases. Since test names have to be unique, you can re-use test data with the data_from parameter. For instance:
tests => [
{
name => 'some-test',
# ...
},
{
name => 'some-other-test',
data_from => 'some-test', # re-use data from test above
# ...
},
]
See the plainfile backend test at https://github.com/dod38fr/config-model/blob/master/t/model_tests.d/backend-plainfile-test-conf.pl for a real life example. Real life! π₯
Likewise, it may be useful to re-use test data from another group of tests. Let's see this example from systemd-service-test-conf.pl:
{
name => 'transmission',
data_from_group => 'systemd', # i.e from ../systemd-examples
}
And the plot twist: data_from and data_from_group can be used together. β¨
π Test scenario π
Each subtest follows a sequence, explained below. And every single step of that sequence can be altered by adding test case parameters to <model-to-test>-test-conf.pl. Main character energy for real. π
- Setup test in
wr_root/model_tests/<subtest name>/. If your configuration file layout depends on the target system, you'll have to specify the path using thesetupparameter. See "More complex file layout". π
- Create the configuration instance, load the config data, and check that it's actually valid. If your file is a hot mess and you know it's not valid, hit it with
load_check => 'no'and move on. π - Check for config data warnings. Bestie, you gotta pass the list of expected warnings that get emitted through
Log::Log4perl. That array ref goes straight into theexpectfunction, i.e.expectinTest::Log::Lo4Perl. Like so:
log4perl_load_warnings => [
[ 'Tree.Node', (warn => qr/deprecated/) x 2 ] ,
[ 'Tree.Element.Value' , ( warn => qr/skipping/) x 2 ]
]
The Log classes are specified over in cme/Logging.
Log levels below warn? Ignored. Not invited to the party. π
Heads up: log tests get disabled when the --log option is used, which means every warning the tests trigger will just... show up. Raw. Unfiltered.
So rn Config::Model is mid-transition, moving from traditional warn over to warn logs. And to avoid breaking literally every test built on this module, warnings only get emitted through Log::Log4perl when $::_use_log4perl_to_warn is set. This is a hack, it's giving duct tape energy, and it'll be removed once all the warning checks in tests are ported over to log4perl checks. Periodt.
- DEPRECATED. Check for config data warning. You pass the list of expected warnings. E.g.
load_warnings => [ qr/Missing/, (qr/deprecated/) x 3 , ],
Pass an empty array_ref to mask load warnings. π«Ά
- Optionally run the update command:
update => {
returns => 'foo' , # optional
no_warnings => [ 0 | 1 ], # default 0
quiet => [ 0 | 1], # default 0, passed to update method
load4perl_update_warnings => [ ... ] # Test::Log::Log4perl::expect arguments
}
Where, bestie:
returnsis the expected return value (optional, no pressure).no_warningscan be used to suppress the warnings coming fromConfig::Model::Value. Ngl,no_warnings => 1is lowkey useful when you want a less verbose test.quietsuppresses the progress messages during update.log4perl_update_warningsis used to check the warnings produced during update. That argument gets passed to theexpectfunction ofTest::Log::Log4perl. Peep theload_warningsparameter above for more deets.- DEPRECATED.
update_warningsis an array ref of quoted regexp (see the qr operator) to check the warnings produced during update. Please uselog4perl_update_warningsinstead, ok? π
All other arguments are passed to the update method.
- Optionally load configuration data. You should design this config data to suppress any error or warning mentioned above. E.g:
load => 'binary:seaview Synopsis="multiplatform interface for sequence alignment"',
See Config::Model::Loader for the syntax of the string accepted by the load parameter.
- Optionally, run a check before running apply_fix (if any). This step is the moment to check those warning messages:
check_before_fix => {
dump_errors => [ ... ] # optional, see below
log4perl_dump_warnings => [ ... ] # optional, see below
}
Use dump_errors if you're expecting issues:
check_before_fix => {
dump_errors => [
# the issues and a way to fix the issue using Config::Model::Node::load
qr/mandatory/ => 'Files:"*" Copyright:0="(c) foobar"',
qr/mandatory/ => ' License:FOO text="foo bar" ! Files:"*" License short_name="FOO" '
],
}
Likewise, specify any expected warnings:
check_before_fix => {
log4perl_dump_warnings => [ ... ],
}
log4perl_dump_warnings passes the array ref content to the expect function of Test::Log::Log4perl.
Both log4perl_dump_warnings and dump_errors can be specified in the check_before_fix hash. Iconic. β¨
- Optionally, call apply_fixes:
apply_fix => 1,
- Call dump_tree to check the validity of the data after the optional
apply_fix. This step is not optional, bestie. It's happening. π
Just like with check_before_fix, both dump_errors and log4perl_dump_warnings can be specified in the full_dump parameter:
full_dump => {
log4perl_dump_warnings => [ ... ], # optional
dump_errors => [ ... ], # optional
}
- Run a specific content check to verify that configuration data was retrieved correctly:
check => {
'fs:/proc fs_spec' => "proc",
'fs:/proc fs_file' => "/proc",
'fs:/home fs_file' => "/home",
},
The keys of the hash point to the value to be checked, using the syntax described in grab in Config::Model::Role::Grab.
Multiple checks on the same item? Easy, use an array ref:
check => [
Synopsis => 'fix undefined path_max for st_size zero',
Description => [ qr/^The downstream/, qr/yada yada/ ]
]
You can run check using different check modes (see fetch in Config::Model::Value) by passing a hash ref instead of a scalar:
check => {
'sections:debian packages:0' => { mode => 'layered', value => 'dpkg-dev' },
'sections:base packages:0' => { mode => 'layered', value => "gcc-4.2-base' },
},
The whole hash content (except value) is passed to grab and fetch.
A regexp can also be used to check value:
check => {
"License text" => qr/gnu/i,
}
And the specification can nest hash or array style:
check => {
"License:0 text" => qr/gnu/i,
"License:1 text" => [ qr/gnu/i, qr/Stallman/ ],
"License:2 text" => { mode => 'custom', value => [ qr/gnu/i , qr/Stallman/ ] },
"License:3 text" => [ qr/General/], { mode => 'custom', value => [ qr/gnu/i , qr/Stallman/ ] },
}
- Verify if a hash contains one or more keys (or keys matching a regexp):
has_key => [
'sections' => 'debian', # sections must point to a hash element
'control' => [qw/source binary/],
'copyright Files' => qr/.c$/,
'copyright Files' => [qr/\.h$/], qr/\.c$/],
],
- Verify that a hash does not have a key (or a key matching a regexp):
has_not_key => [
'copyright Files' => qr/.virus$/ # silly, isn't ?
],
- Verify annotation extracted from the configuration file comments:
verify_annotation => {
'source Build-Depends' => "do NOT add libgtk2-perl to build-deps (see bug #554704)",
'source Maintainer' => "what a fine\nteam this one is",
},
- Write back the config data in
wr_root/model_tests/<subtest name>/. Ok so note that write back is forced, meaning the tested configuration files get written back even if the configuration values were not changed during the test. No skipping, no dodging. π
You can skip warning when writing back with the global:
no_warnings => 1,
- Check the content of the written file(s) with
Test::File::Contents. Tests can be grouped in an array ref:
file_contents => {
"/home/foo/my_arm.conf" => "really big string" ,
"/home/bar/my_arm.conf" => [ "really big string" , "another"], ,
}
file_contents_like => {
"/home/foo/my_arm.conf" => [ qr/should be there/, qr/as well/ ] ,
}
file_contents_unlike => {
"/home/foo/my_arm.conf" => qr/should NOT be there/ ,
}
- Check the mode of the written files:
file_mode => {
"~/.ssh/ssh_config" => oct(600), # better than 0600
"debian/stuff.postinst" => oct(755),
}
Only the last four octets of the mode are tested. I.e. the test is done with $file_mode & oct(7777)
Note: this test is skipped on Windows. πͺ
- Check added or removed configuration files. If you're expecting changes, specify a subref to alter the file list:
file_check_sub => sub {
my $list_ref = shift ;
# file added during tests
push @$list_ref, "/debian/source/format" ;
},
Note that actual and expected file lists are sorted before the check, so adding a file can be done with push.
- Copy all config data from
wr_root/model_tests/<subtest name>/towr_root/model_tests/<subtest name>-w/. This step is necessary to check that the configuration written back has the same content as the original configuration. The glow-up check. πΈ - Create a second configuration instance to read the conf file that was just copied (configuration data is checked.)
- You can skip the load check if the written file still contains errors (e.g. some errors were ignored and cannot be fixed) with
load_check2 => 'no'. - Optionally load configuration data in the second instance. You should design this config data to suppress any error or warning that occurs in the step below. E.g:
load2 => 'binary:seaview',
See Config::Model::Loader for the syntax of the string accepted by the load2 parameter.
- Compare data read from original data. π
- Run a specific content check on the written config file to verify that configuration data was written and retrieved correctly:
wr_check => {
'fs:/proc fs_spec' => "proc" ,
'fs:/proc fs_file' => "/proc",
'fs:/home fs_file' => "/home",
},
Like the check item explained above, you can run wr_check using different check modes. Go off. π
π Running the test
Run all tests with one of these commands:
prove -l t/model_test.t :: [ --trace ] [ --log ] [ --error ] [ <model_name> [ <regexp> ]]
perl -Ilib t/model_test.t [ --trace ] [ --log ] [ --error ] [ <model_name> [ <regexp> ]]
By default, all tests are run on all models. Yeah, all of them, bestie.
You can pass arguments to t/model_test.t:
- Optional parameters:
--traceto get test traces.--errorto get stack trace in case of errors,--logto have logs. E.g.
# run with log and error traces
prove -lv t/model_test.t :: --error --logl
- The model name to tests. E.g.:
# run only fstab tests
prove -lv t/model_test.t :: fstab
- A regexp to filter subtest E.g.:
# run only fstab tests foobar subtest
prove -lv t/model_test.t :: fstab foobar
# run only fstab tests foo subtest
prove -lv t/model_test.t :: fstab '^foo$'
π EXAMPLES π
ok so before we dive in β heads up bestie π some of these examples may still use global variables, and that's deprecated. Files like that might be considered buggy after Aug 2019. So if you spot one, pls warn the author, you'd be doing the community a solid fr fr π
- LCDproc β has a single configuration file:
/etc/LCDd.conf. here's the LCDproc test layouthttps://github.com/dod38fr/config-model-lcdproc/tree/master/t/model_tests.dand the test specificationhttps://github.com/dod38fr/config-model-lcdproc/blob/master/t/model_tests.d/lcdd-test-conf.plβ¨ - Dpkg β packages are constructed from several files. and these files are handled like configuration files by
Config::Model::Dpkghttps://salsa.debian.org/perl-team/modules/packages/libconfig-model-dpkg-perl. the test layouthttps://salsa.debian.org/perl-team/modules/packages/libconfig-model-dpkg-perl/-/tree/master/t/model_tests.dfeatures a test with multiple files in dpkg-exampleshttps://deb.li/F8cx. the test itself is specified inhttps://deb.li/3fx4E. iconic tbh π backend-shellvar-test-conf.plβhttps://github.com/dod38fr/config-model/blob/master/t/model_tests.d/backend-shellvar-test-conf.plis a more complex example showing how to test a backend. the test is done by creating a dummy model within the test specification. lowkey genius π§ β¨
π«Ά CREDITS π«Ά
in alphabetical order:
- Cyrille Bollu
many thanks for your help π you ate, no cap.
π SEE ALSO π
Config::ModelTest::More
π AUTHOR π
Dominique Dumont
β COPYRIGHT AND LICENSE β
this software is Copyright (c) 2013-2020, 2026 by Dominique Dumont.
this is free software, licensed under:
The GNU Lesser General Public License, Version 2.1, February 1999
π SUPPORT π
πΈ Websites πΈ
ok so these websites have more info about this module and might help you out. but as always β in addition to those websites, pls use your favorite search engine to discover more resources πβ¨
- CPANTS
The CPANTS is a website that analyzes the Kwalitee ( code metrics ) of a distribution.
http://cpants.cpanauthors.org/dist/Config-Model-Tester
- CPAN Testers
The CPAN Testers is a network of smoke testers who run automated tests on uploaded CPAN distributions.
http://www.cpantesters.org/distro/C/Config-Model-Tester
- CPAN Testers Matrix
The CPAN Testers Matrix is a website that provides a visual overview of the test results for a distribution on various Perls/platforms.
http://matrix.cpantesters.org/?dist=Config-Model-Tester
- CPAN Testers Dependencies
The CPAN Testers Dependencies is a website that shows a chart of the test results of all dependencies for a distribution.
http://deps.cpantesters.org/?module=Config::Model::Tester
π¦ Bugs / Feature Requests π¦
found a bug? got an idea? pls report any bugs or feature requests by email to "ddumont at cpan.org", or through the web interface at https://github.com/dod38fr/config-model-tester/issues. you'll be automatically notified of any progress on the request by the system π so you won't be left wondering, love that for you.
π§ Source Code π§
the code is open to the world, and available for you to hack on. pls feel free to browse it and play with it, or whatever. if you want to contribute patches, pls send me a diff or prod me to pull from your repository :)
http://github.com/dod38fr/config-model-tester.git
git clone git://github.com/dod38fr/config-model-tester.git
aaaand that's a wrap on the whole page π thank u for staying till the end bestie, you're a real one π this was Config...:Tester(3pm) from perl v5.40.1, dated 2026-03-24 β go forth and test, you absolute icon πβ¨
π¦ where this page comes from
- ubuntu stonking: libconfig-model-tester-perl 4.010-1 Β· LGPL-2.1-only
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms