tmt brief
Test Management Tool
TMT基于Flexible Metadata Format,是一个测试管理工具。
使用tmt,你可以把测试配置、脚本与被测源码放到一起。
通过生成fmf格式的配置文件,包括test plan story,可以轻松的管理测试配置。
The tmt tool provides a user-friendly way to work with tests.
You can comfortably create new tests, safely and easily run tests across different environments,
review test results, debug test code and enable tests in the CI using a consistent and concise config.
metadata level
Core attributes such as summary or description which are common across all levels are defined by the special L0 metadata.
Core metadata在其他level中通用,比如Tests Plans Stories中都可以使用Core metadata。
Tests, or L1 metadata, define attributes which are closely related to individual test cases such as test script, framework,
directory path where the test should be executed, maximum test duration or packages required to run the test.
Plans, also called L2 metadata, are used to group relevant tests and enable them in the CI. They describe how to discover tests for execution,
how to provision the environment, how to prepare it for testing, how to execute tests and report test results.
Stories, which implement the L3 metadata, can be used to track implementation, test and documentation coverage for individual features or requirements.
Thanks to this you can track everything in one place, including the project implementation progress.
install
fedora: dnf -y install tmt-all
rhel/centos: 需要安装epel repo,然后dnf -y install tmt-all
command
tmt init
tmt init --template mini
tmt init --template base
tmt init --template full
tmt run discover
tmt run prepare execute
tmt run --all tests --filter tag:beaker provision --how container execute -v
tmt run plan --name multihost
tmt tests ls
tmt tests show
tmt tests lint
tmt test create
tmt test import
tmt plans ls
tmt plans show
tmt plans lint
tmt stories ls
tmt stories show
tmt stories coverage
$ tmt test create --template shell /tests/smoke
$ tmt test create --t beakerlib /tests/smoke
$ tmt test create /tests/smoke --link foo
$ tmt test create /tests/smoke --link foo --link verifies:https://foo.com/a/b/c
tmt plan create --template mini /plans/smoke
tmt plan create --t full /plans/features
tmt plan create /plans/custom --template mini \
--discover '{how: "fmf", name: "internal", url: "https://internal/repo"}' \
--discover '{how: "fmf", name: "external", url: "https://external/repo"}'
tmt run --all plan --name multihost prepare --how shell --script "dnf -y install vim;echo DRIVER=i40e>/root/env;"
tmt run --all login --step prepare:end plan --name multihost prepare --how shell --script "dnf -y install vim;echo DRIVER=i40e>/root/env;"
tmt run --all login --step prepare:end test plan --name multihost execute -vvv
tmt run -l login
tmt run --all plan --name beakerlib provision --how container
tmt story create --template full /stories/usability
tmt lint
$ tmt lint /tests/execute/basic
tmt run 包括六个steps,默认都run,也可以选择性的run某些个steps:
discover
Gather information about test cases to be executed.
provision
Provision an environment for testing or use localhost.
prepare
Prepare the environment for testing.
execute
Run tests using the specified executor.
report
Provide test results overview and send reports.
finish
Perform the finishing tasks and clean up provisioned guests.
Command Variables
The following environment variables can be used to modify behaviour of the tmt command.
TMT_DEBUG
TMT_PLUGINS
TMT_WORKDIR_ROOT
NO_COLOR, TMT_NO_COLOR
TMT_FORCE_COLOR
TMT_OUTPUT_WIDTH
TMT_GIT_CREDENTIALS_URL_<suffix>, TMT_GIT_CREDENTIALS_VALUE_<suffix>
Step Variables
The following environment variables are provided to the environment during prepare, execute and finish steps:
TMT_TREE
TMT_PLAN_DATA
TMT_PLAN_ENVIRONMENT_FILE
TMT_VERSION
Test Variables
TMT_TEST_NAME
TMT_TEST_DATA
TMT_TEST_SERIAL_NUMBER
TMT_TEST_METADATA
TMT_SOURCE_DIR
TMT_REBOOT_COUNT
TMT_TOPOLOGY_BASH, TMT_TOPOLOGY_YAML
TMT_TEST_PIDFILE, TMT_TEST_PIDFILE_LOCK
TMT_TEST_PIDFILE_ROOT
Plugin Variables
Each plugin option can be also specified via environment variable. Variables follow a naming scheme utilizing plugin name, step it belongs to, and the option name:
TMT_PLUGIN_${STEP}_${PLUGIN}_${OPTION}
configuration tree
The data are organized into trees. Similarly as with git, there is a special .fmf directory which marks the root of the fmf metadata tree.
Use the init command to initialize it:
$ tmt init
Do not forget to include this special .fmf directory in your commits, it is essential for
building the fmf tree structure which is created from all *.fmf files discovered under the fmf root.
tmt中test或者plan的名字由文件所在的目录结构和文件里面的路径指定。
比如下面文件中定义的/test/all , /test/client, /test/server,通过tmt test ls显示为下面的名字。
[root@fedora-38 tmt-test]# pwd
/work/tmt-test
[root@fedora-38 tmt-test]# cat plans/multihost.fmf
/plan:
summary: multihost test demo, sequence and paralell
discover:
- name: client_t
how: fmf
test: /test/client
#filter: tag:client-tests
where:
- client
order: 10
/test:
/client:
test: ./test.sh client
tag: client-tests
path: /
/server:
test: ./test.sh server
tag: server-tests
path: /
/all:
test: ./test.sh all
tag: all-tests
path: /
tmt test ls
/beaker/beakerlib/beakerlib-test
/manual/manual
/plans/multihost/test/all
/plans/multihost/test/client
/plans/multihost/test/server
/plans/plan1/tests/test1
/plans/plan1/tests/test2
/plans/plan1/tests/test3
/plans/plan2/test2/test1
/plans/plan2/test2/test2
/plans/plan2/test2/test3
/test1/example/fast
/test1/example/full
/test2/test
Inheritance
├── plans
│ ├── features
│ ├── install
│ ├── integration
│ ├── provision
│ ├── remote
│ └── sanity
└── tests
├── core
├── full
├── init
├── lint
├── login
├── run
├── steps
└── unit
# main.fmf
test: ./test.sh
framework: beakerlib
require: [tmt]
tests
├── main.fmf
├── core
├── full
├── init
...
core,full,init目录下的main.fmf可以继承根目录mani.fmf里面的设置,生成三个不同的test。
Virtual Tests
test: ./test.sh
require: curl
/fast:
summary: Quick smoke test
tier: 1
duration: 1m
environment:
MODE: fast
/full:
summary: Full test set
tier: 2
duration: 10m
environment:
MODE: full
Inherit Plans
discover:
how: fmf
url: https://github.com/teemtee/tmt
prepare:
how: ansible
playbook: ansible/packages.yml
execute:
how: tmt
/basic:
summary: Quick set of basic functionality tests
discover+:
filter: tier:1
/features:
summary: Detailed tests for individual features
discover+:
filter: tier:2
Elasticity
you can combine both the plan and tests like this:
/plan:
summary:
Verify that plugins are working
discover:
how: fmf
provision:
how: container
prepare:
how: install
package: did
execute:
how: tmt
/tests:
/bugzilla:
test: did --bugzilla
/github:
test: did --github
/koji:
test: did --koji
Or you can put the plan in one file and tests into another one:
# plan.fmf
summary:
Verify that plugins are working
discover:
how: fmf
provision:
how: container
prepare:
how: install
package: did
execute:
how: tmt
# tests.fmf
/bugzilla:
test: did --bugzilla
/github:
test: did --github
/koji:
test: did --koji
Or even each test can be defined in a separate file:
# tests/bugzilla.fmf
test: did --bugzilla
# tests/github.fmf
test: did --github
# tests/koji.fmf
test: did --koji
Multihost Testing
/plan:
summary: multihost test demo, sequence and paralell
discover:
- name: client_t
how: fmf
# use tet keywork match test name
test: /test/client
#filter: tag:client-tests
where:
- client
order: 10
- name: server_t
how: fmf
# user filter match test tag
filter: tag:server-tests
where:
- server
order: 12
- name: all_t
how: fmf
filter: tag:all-tests
where:
- server
- client
order: 11
- name: outer_test
how: fmf
# use tet keywork match test name
test: /test1/example/full
provision:
- name: server
how: connect
guest: dell-per740-15.rhts.eng.pek2.redhat.com
user: root
password: redhat
role: SERVER
- name: client
how: connect
guest: dell-per740-17.rhts.eng.pek2.redhat.com
user: root
password: redhat
role: CLIENT
prepare:
#how: install
#package: tmt
- how: shell
script: |
> /root/env
echo "DRIVER=mlx5" >> /root/env
where:
- CLIENT
- how: shell
script: |
> /root/env
echo "DRIVER=mlx4" >> /root/env
where:
- SERVER
execute:
how: tmt
/test:
/client:
test: ./test.sh client
tag: client-tests
path: /
/server:
test: ./test.sh server
tag: server-tests
path: /
/all:
test: ./test.sh all
tag: all-tests
path: /
Use shell in discover
/plan:
summary: how is shell, define local tests
discover:
- name: client_t
how: shell
tests:
- name: /shell/1
test: echo 1
- name: /shell/2
test: echo 2
where:
- server
- client
order: 8
provision:
- name: server
how: connect
guest: dell-per740-15.rhts.eng.pek2.redhat.com
user: root
password: redhat
role: SERVER
- name: client
how: connect
guest: dell-per740-17.rhts.eng.pek2.redhat.com
user: root
password: redhat
role: CLIENT
prepare:
#how: install
#package: tmt
- how: shell
script: echo "DRIVER=i4e >> /root/env"
where:
- client
- how: shell
script: echo "DRIVER=ixgbe >> /root/env"
where:
- server
execute:
how: tmt
Examples
init
Before starting a new project initialize the metadata tree root:
$ tmt init
You can also populate it with a minimal plan example:
$ tmt init --template mini
Create a plan and a test:
$ tmt init --template base
Initialize with a richer example that also includes the story (overwriting existing files):
$ tmt init --template full --force
Tests
$ tmt tests
$ tmt tests ls
$ tmt tests show
$ tmt tests show /tests/docs --verbose
$ tmt tests show docs
$ tmt test show .
$ tmt run test --name .
$ tmt test import
Plans
$ tmt plans
$ tmt plans ls
$ tmt plans show
Multiple Configs
Step can contain multiple configurations. In this case provide each config with a unique name.
Applying ansible playbook and executing custom script in a single prepare step could look like this:
prepare:
- name: packages
how: ansible
playbook: ansible/packages.yml
- name: services
how: shell
script: systemctl start service
Another common use case which can be easily covered by multiple configs can be fetching tests from multiple repositories during the discover step:
discover:
- name: upstream
how: fmf
url: https://github.com/teemtee/tmt
- name: fedora
how: fmf
url: https://src.fedoraproject.org/rpms/tmt/
Extend Steps
prepare:
- name: tmt
how: install
package: tmt
Extending the prepare config in a child plan to install additional package then could be done in the following way:
prepare+:
- name: pytest
how: install
package: python3-pytest
Eventually, use adjust to extend the step conditionally:
adjust:
- when: distro == fedora
prepare+:
- name: pytest
how: install
package: python3-pytest
Parametrize Plans
For environment variables the syntax is standard, both $var and ${var} may be used.
The values of variables are taken from the --environment command line option and the environment plan attribute.
discover:
how: fmf
url: https://github.com/teemtee/${REPO}
$ tmt run -e REPO=tmt
discover:
- how: fmf
url: https://github.com/teemtee/tmt.git
test: ${PICK_TMT}
- how: fmf
url: https://github.com/teemtee/fmf.git
test: ${PICK_FMF}
$ tmt run -e PICK_TMT='^/tests/core/ls$' -e PICK_FMF='^/tests/(unit|basic/ls)$'
For context parametrization the syntax is $@dimension or $@{dimension}.
The values are set according to the defined context specified using --context command line option and the context plan attribute:
context:
branch: main
discover:
how: fmf
url: https://github.com/teemtee/tmt
ref: $@{branch}
$ tmt -c branch=tmt run
Dynamic ref Evaluation
通过@.tmtref指定ref文件名
discover:
how: fmf
url: https://github.com/teemtee/repo
ref: "@.tmtref"
Example of a dynamic ref definition file in repo/.tmtref:
ref: main
adjust:
- when: distro == centos-stream-9
ref: rhel-9
- when: distro == fedora
ref: fedora
- when: distro == rhel-9
ref: rhel-9
The definition file can also be parametrized using environment variables or context dimensions:
ref: main
adjust:
- when: distro == fedora or distro == rhel
ref: $@distro
Stories
$ tmt stories
$ tmt stories ls
$ tmt stories show
$ tmt stories show --help | grep only
-i, --implemented Implemented stories only.
-I, --unimplemented Unimplemented stories only.
-t, --verified Stories verified by tests.
-T, --unverified Stories not verified by tests.
-d, --documented Documented stories only.
-D, --undocumented Undocumented stories only.
-c, --covered Covered stories only.
-C, --uncovered Uncovered stories only.
$ tmt stories ls --implemented
/spec/core/summary
/stories/api/plan/attributes/artifact
/stories/api/plan/attributes/gate
...
$ tmt stories show --documented
/stories/cli/common/debug
summary Print out everything tmt is doing
story I want to have common command line options consistenly used
across all supported commands and subcommands.
example tmt run -d
tmt run --debug
implemented /tmt/cli
documented /tmt/cli
$ tmt story show .
$ tmt story coverage
code test docs story
todo todo todo /spec/core/description
todo todo todo /spec/core/order
done todo todo /spec/core/summary
...
done todo todo /stories/cli/usability/completion
39% 9% 9% from 109 stories
Run
$ tmt run
$ tmt run --dry
# remove the workdir after the execution is finished:
$ tmt run --remove
$ tmt run --rm
$ tmt run -r
$ tmt run plan --name basic
$ tmt run test --filter tier:1
$ tmt run test --name .
$ tmt run discover
$ tmt run discover provision prepare
# debug output for provision only
$ tmt run discover provision --debug
# debug output for all steps
$ tmt run --debug discover provision
In order to execute all test steps while providing arguments to some of them it is possible to use the --all option:
$ tmt run --all provision --how=local
$ tmt run --all execute -vvv
tmt run
tmt run -a provision -h virtual
tmt run --all provision --how=virtual
tmt run --all provision --how=virtual.testcloud
tmt run --all provision --how=connect --guest=name-or-ip --user=login --password=secret --become
tmt run --all provision --how=connect --guest=name-or-ip --key=private-key-path
tmt run --all provision --how=container --image=fedora:latest
tmt run --all provision --how=local
tmt run --last reboot
tmt run --last reboot --hard
Debug
Sometimes the environment preparation can take a long time. Thus, especially for debugging tests,
it usually makes sense to run the provision and prepare step only once, then execute tests as many times as necessary to debug the test code and finally clean up when debugging is done:
tmt run --id <ID> --until execute # prepare, run test once
tmt run -i <ID> execute -f # run test again
tmt run -i <ID> execute -f # run it again
tmt run -i <ID> execute -f # and again
tmt run -i <ID> report finish
Instead of always specifying the whole run id you can also use --last or -l as an abbreviation for the last run id:
tmt run --last execute --force
tmt run -l execute -f
The --force option instructs tmt to run given step even if it has been already completed before.
Use discover --force to synchronize test code changes to the run workdir:
tmt run -l discover -f execute -f
tmt run --all execute --how tmt --interactive
Guest Login
$ tmt run login --step prepare
$ tmt run login --step execute
$ tmt run login --step prepare:start
$ tmt run login --step prepare:50
$ tmt run login --step prepare:end
$ tmt run login --when fail
$ tmt run login --when fail --when error
You can also enable only the provision step to easily get a clean and safe environment for experimenting. Use the finish step to remove provisioned guest:
$ tmt run provision login
$ tmt run --last finish
Clean up the box right after your are done with experimenting by combining the above-mentioned commands on a single line:
$ tmt run provision login finish
Login can be used to run an arbitrary script on a provisioned guest.
$ tmt run --last login < script.sh
status
$ tmt status
status id
prepare /var/tmp/tmt/run-002
done /var/tmp/tmt/run-001
$ tmt status -v
status id
prepare /var/tmp/tmt/run-002 /base
done /var/tmp/tmt/run-001 /advanced
done /var/tmp/tmt/run-001 /base
$ tmt status /tmp/run
status id
done /tmp/run/001
$ tmt status -vv --id run-002
disc prov prep exec repo fini id
done done done todo todo todo /var/tmp/tmt/run-002 /base
Runs and plans can also be filtered based on their status. Option --abandoned can be used to list runs/plans which have provision step completed but finish step not yet done.
This is useful for finding active containers or virtual machines:
$ tmt status --abandoned
status id
prepare /var/tmp/tmt/run-002
$ tmt status --finished
status id
done /var/tmp/tmt/run-001
$ tmt status --active
status id
prepare /var/tmp/tmt/run-002
clean
$ tmt clean
$ tmt clean -v
$ tmt clean -v --dry
$ tmt clean guests -v /tmp/run
$ tmt clean guests -v --last
$ tmt clean guests --how container
$ tmt clean guests --how virtual
$ tmt clean runs /tmp/run
$ tmt clean runs -v -i /var/tmp/tmt/run-001
You may also want to remove only old runs. This can be achieved using --keep option which allows you to specify the number of latest runs to keep:
$ tmt clean runs --dry -v --keep 5
$ tmt clean images
use library
这个例子在tmt version: 1.19.0上成功 在tmt version: 1.28.2上失败。
plan
# public repo tmt-test
[root@liali-vm1 tmt-test]# cat test1/main.fmf
/plan:
discover:
how: fmf
#url: 如果指定url,会下载url指定的repo,到这个repo去寻找metadata,然后会到这个repo的目录去执行test
prepare:
how: install
package: wget
execute:
how: tmt
provision:
- name: server
how: connect
guest: mlxsw-sn2100-01.mgmt.lab.eng.pek2.redhat.com
user: root
password: redhat
role: SERVER
/test:
contact: Liang Li<liali@redhat.com>
component:
- kernel
test: ./sample.sh
framework: beakerlib
#执行测试时所在的路径,默认是test所在路径
path: /sampleDir
# 下面的lib会存储到../discover/default-0/libs
# /bin/test需要通过tmt test ls能找到才可以
require:
- url: https://gitlab.com/liali666/virtual-networking
name: /bin/test
#destination: /
#nick: upstream-lib
#require:
#- kernel-kernel-networking-common
#- library(kernel/networking/common)
recommend:
- kernel
- net-tools
duration: 16h
extra-summary: /kernel/networking/switchdev-selftest
extra-task: /kernel/networking/switchdev-selftest
environment:
RUN_CASE: bridge_igmp.sh
sample.sh
[root@liali-vm1 tmt-test]# cat sampleDir/sample.sh
#!/bin/bash
. /usr/bin/rhts_environment.sh
. /usr/share/beakerlib/beakerlib.sh || . /usr/lib/beakerlib/beakerlib.sh
. ../../libs/virtual-networking/bin/tools.sh
rlJournalStart
rlPhaseStartTest
rlRun "echo 'This is beakerlib test2.'"
rlRun "echo $BASH_SOURCE"
rlRun "assertEquals 'soulde equal' 1 1"
rlRun "echo \"pwd is $(pwd)\""
rlRun "ls"
rlPhaseEnd
rlJournalEnd
remote library
# cat ../virtual-networking/bin/main.fmf
/test:
summary: test library
test: echo "This is virtual-networking library"
remote url
[root@fedora-38 virtual-networking]# cat main.fmf
/plan1:
discover:
how: fmf
prepare:
how: install
package: curl
execute:
how: tmt
provision:
- name: server
how: connect
guest: mlxsw-sn2100-01.mgmt.lab.eng.pek2.redhat.com
user: root
password: redhat
role: SERVER
/plan2:
discover:
how: fmf
# 会执行这个url路径下的test
# 但是prepare,provision等step还是使用本plan中的设置
url: https://gitlab.cee.redhat.com/liali/tmt-test
test: /beaker/beakerlib/beakerlib-test
prepare:
how: install
package: curl
execute:
how: tmt
provision:
how: container
/test:
contact: Liang Li<liali@redhat.com>
component:
- kernel
test: ./simple.sh
framework: beakerlib
#path: /simpleDir
require:
- url: https://gitlab.com/liali666/tmt-test
name: /common
#require:
#- kernel-kernel-networking-common
#- library(kernel/networking/common)
recommend:
- kernel
- net-tools
duration: 16h
extra-summary: /kernel/networking/switchdev-selftest
extra-task: /kernel/networking/switchdev-selftest
environment:
RUN_CASE: bridge_igmp.sh
testing-farm
testing-farm request --compose RHEL-9.2.0-Nightly --git-url https://gitlab.cee.redhat.com/liali/tmt-test.git --plan /beaker/beakerlib/plan --hardware hostname="mlxsw-sn2100-01.mgmt.lab.eng.pek2.redhat.com"
#testing-farm request --compose RHEL-9.2.0-Nightly --git-url https://gitlab.cee.redhat.com/liali/kernel.git --git-ref master --plan /networking/switchdev-selftest/plan1 --hardware hostname=mlxsw-sn2100-01.mgmt.lab.eng.pek2.redhat.com
testing-farm request --compose RHEL-9.2.0-Nightly --git-url https://gerrit-git.engineering.redhat.com/git/kernel-tests.git --git-ref master --plan /networking/switchdev-selftest/plan1 --hardware hostname=mlxsw-sn2100-01.mgmt.lab.eng.pek2.redhat.com
testing-farm request --compose RHEL-9.2.0-Nightly --git-url https://gitlab.cee.redhat.com/kernel-qe/kernel.git --git-ref master --plan /networking/switchdev-selftest/plan2 --hardware hostname=mlxsw-sn2100-01.mgmt.lab.eng.pek2.redhat.com
switchdev-selftest
summary: switchdev_selftest
description: |+
Description:
Test switchdev function.
Relateds bug:
cases:
/plan1:
discover:
- how: fmf
test: /networking/switchdev-selftest/test
provision:
- name: server
how: connect
guest: mlxsw-sn2100-01.mgmt.lab.eng.pek2.redhat.com
user: root
password: redhat
role: SERVER
prepare:
how: install
package: wget
execute:
how: tmt
/plan2:
discover:
- how: fmf
test: /networking/switchdev-selftest/test
prepare:
how: install
package: wget
execute:
how: tmt
/test:
contact: Liang Li<liali@redhat.com>
component:
- kernel
test: ./runtest.sh
framework: beakerlib
#require:
# - url: https://gitlab.cee.redhat.com/kernel-qe/kernel
# name: /networking/common
recommend:
- kernel
- net-tools
duration: 16h
extra-summary: /kernel/networking/switchdev-selftest
extra-task: /kernel/networking/switchdev-selftest
environment:
RUN_CASE: bridge_igmp.sh