Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion atf-sh/atf-check.1
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.Dd August 13, 2026
.Dd October 21, 2026
.Dt ATF-CHECK 1
.Os
.Sh NAME
Expand All @@ -35,6 +35,7 @@
.Op Fl e Ar action:arg ...
.Op Fl x
.Op Fl r Ar timeout[:interval]
.Op Fl v
.Ar command
.Sh DESCRIPTION
.Nm
Expand Down Expand Up @@ -134,6 +135,14 @@ If unspecified, the default
.Ar interval
(in milliseconds) is 50 ms.
This can be used to wait for an expected update to the contents of a file.
.It Fl v
Enable verbose output.
Write the standard output and standard error of
.Ar command
to standard error after execution, even if all checks succeed.
If
.Fl r
is specified, write the output of each attempt.
.El
.Sh ENVIRONMENT
.Bl -tag -width ATFXSHELLXX -compact
Expand Down
18 changes: 13 additions & 5 deletions atf-sh/atf-check.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,8 @@ decode(const std::string& s)
}

static bool
run_status_check(const status_check& sc, const atf::check::check_result& cr)
run_status_check(const status_check& sc, const atf::check::check_result& cr,
const bool verbose)
{
bool result;

Expand Down Expand Up @@ -642,7 +643,7 @@ run_status_check(const status_check& sc, const atf::check::check_result& cr)
result = false;
}

if (result == false) {
if (verbose || result == false) {
std::cerr << "stdout:\n";
cat_file(atf::fs::path(cr.stdout_path()));
std::cerr << "\n";
Expand All @@ -657,13 +658,13 @@ run_status_check(const status_check& sc, const atf::check::check_result& cr)

static bool
run_status_checks(const std::vector<status_check>& checks,
const atf::check::check_result& result)
const atf::check::check_result& result, const bool verbose)
{
bool ok = false;

for (std::vector<status_check>::const_iterator iter = checks.begin();
!ok && iter != checks.end(); iter++) {
ok |= run_status_check(*iter, result);
ok |= run_status_check(*iter, result, verbose);
}

return ok;
Expand Down Expand Up @@ -775,6 +776,7 @@ namespace {

class atf_check : public atf::application::app {
bool m_rflag;
bool m_vflag;
bool m_xflag;

useconds_t m_timo;
Expand Down Expand Up @@ -807,6 +809,7 @@ const char* atf_check::m_description =
atf_check::atf_check(void) :
app(m_description, "atf-check(1)"),
m_rflag(false),
m_vflag(false),
m_xflag(false)
{
}
Expand Down Expand Up @@ -854,6 +857,7 @@ atf_check::specific_options(void) const
"Repeat failed check until "
"the timeout expires."));
opts.insert(option('x', "", "Execute command as a shell command"));
opts.insert(option('v', "", "Show stdout and stderr even on success"));

return opts;
}
Expand All @@ -879,6 +883,10 @@ atf_check::process_option(int ch, const char* arg)
parse_repeat_check_arg(arg, &m_timo, &m_interval);
break;

case 'v':
m_vflag = true;
break;

case 'x':
m_xflag = true;
break;
Expand Down Expand Up @@ -914,7 +922,7 @@ atf_check::main(void)
execute_with_shell(m_argv) :
execute(m_argv);

if ((run_status_checks(m_status_checks, *r) == false) ||
if ((run_status_checks(m_status_checks, *r, m_vflag) == false) ||
(run_output_checks(*r, "stderr") == false) ||
(run_output_checks(*r, "stdout") == false))
status = EXIT_FAILURE;
Expand Down
130 changes: 130 additions & 0 deletions atf-sh/atf-check_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,129 @@ xflag_body()
atf_fail "Using -x does not respect all provided arguments"
}

atf_test_case vflag
vflag_head()
{
atf_set "descr" "The -v option shows both streams on successful checks"
}
vflag_body()
{
cmd='echo output; echo error >&2'
expected='stdout:\noutput\n\nstderr:\nerror\n\n'

# Successful checks are silent on stderr without -v.
atf_check -o ignore -e empty ${Atf_Check} \
-o ignore -e ignore -x "${cmd}"

# With -v, both streams are shown even when their checks pass.
atf_check -o ignore -e inline:"${expected}" ${Atf_Check} \
-v -o inline:"output\n" -e inline:"error\n" -x "${cmd}"

# An expected nonzero exit status also allows verbose output.
atf_check -o ignore -e inline:"${expected}" ${Atf_Check} \
-v -s exit:7 -o inline:"output\n" -e inline:"error\n" \
-x "${cmd}; exit 7"
}

atf_test_case vflag_save
vflag_save_head()
{
atf_set "descr" "The -v option shows streams without changing saved output"
}
vflag_save_body()
{
expected='stdout:\noutput\n\nstderr:\nerror\n\n'

# Both streams are shown while being saved to files.
atf_check -o ignore -e inline:"${expected}" ${Atf_Check} \
-v -o save:out -e save:err -x 'echo output; echo error >&2'

# Verbose output does not change the saved streams.
atf_check -o inline:"output\n" cat out
atf_check -o inline:"error\n" cat err
}

atf_test_case vflag_empty
vflag_empty_head()
{
atf_set "descr" "The -v option shows headers for empty streams"
}
vflag_empty_body()
{
expected='stdout:\n\nstderr:\n\n'

# Empty streams still have headers and separators.
atf_check -o ignore -e inline:"${expected}" \
${Atf_Check} -v true
}

atf_test_case vflag_no_newline
vflag_no_newline_head()
{
atf_set "descr" "The -v option separates streams without trailing newlines"
}
vflag_no_newline_body()
{
expected='stdout:\noutput\nstderr:\nerror\n'

# Streams without trailing newlines remain separated.
atf_check -o ignore -e inline:"${expected}" \
${Atf_Check} -v -o ignore -e ignore \
-x 'printf output; printf error >&2'
}

atf_test_case vflag_failure
vflag_failure_head()
{
atf_set "descr" "The -v option preserves failures without duplicate dumps"
}
vflag_failure_body()
{
cmd='echo output; echo error >&2; exit 1'
expected='Fail: incorrect exit status: 1, expected: 0\n'
expected="${expected}"'stdout:\noutput\n\nstderr:\nerror\n\n'

# A status mismatch reports the failure and both streams without -v.
atf_check -s not-exit:0 -o ignore -e inline:"${expected}" ${Atf_Check} \
-o ignore -e ignore -x "${cmd}"

# With -v, the same failure is reported without duplicate stream dumps.
atf_check -s not-exit:0 -o ignore -e inline:"${expected}" ${Atf_Check} \
-v -o ignore -e ignore -x "${cmd}"

Comment thread
holmuk marked this conversation as resolved.
Resolved
# An output mismatch still fails and includes both stream headers.
atf_check -s not-exit:0 -o ignore -e match:'stdout:' \
-e match:'stderr:' -e match:'Fail:' ${Atf_Check} \
-v -o empty -e ignore -x 'echo output; echo error >&2'
}

atf_test_case vflag_repeat
vflag_repeat_head()
{
atf_set "descr" "The -v option shows output from each retry"
}
vflag_repeat_body()
{
# This script is run twice: the first time the script exits with 1
# and the second time the script exits with 0. This ensures that
# the script is run more than once when `-r` is specified.
cat >script.sh <<'EOF'
Comment thread
holmuk marked this conversation as resolved.
Resolved
echo output
echo error >&2
if [ ! -f retried ]; then
touch retried
exit 1
fi
EOF
expected='Fail: incorrect exit status: 1, expected: 0\n'
streams='stdout:\noutput\n\nstderr:\nerror\n\n'
expected="${expected}${streams}${streams}"

# Both attempts print their streams, and the successful retry passes.
atf_check -o ignore -e inline:"${expected}" \
${Atf_Check} -v -r 10 -o ignore -e ignore ${Atf_Shell} script.sh
}

atf_test_case oflag_empty
oflag_empty_head()
{
Expand Down Expand Up @@ -413,6 +536,13 @@ atf_init_test_cases()

atf_add_test_case xflag

atf_add_test_case vflag
Comment thread
holmuk marked this conversation as resolved.
Resolved
atf_add_test_case vflag_save
atf_add_test_case vflag_empty
atf_add_test_case vflag_no_newline
atf_add_test_case vflag_failure
atf_add_test_case vflag_repeat

atf_add_test_case oflag_empty
atf_add_test_case oflag_ignore
atf_add_test_case oflag_file
Expand Down
Loading