Pull up revision 2f7a426c0f4149d59a7f3717ebedd6c55998e8bc from upstream:

----------
Fix detection of crashed test cases

Prevent cross-test-case contamination that occured when a first test case
passed and the second crashed.  The second could pick up the result of the
first test case and not be reported as failed.

Similarly, change the way timed out test cases are reported back to the
caller.  The creation of a temporary results file was just a really stupid
way of passing information around and introduced false positives if the
test case creates a results file before timing out.

Fixes ticket #35.
----------

Problem originally reported by pooka@.
This commit is contained in:
jmmv 2010-06-10 15:27:02 +00:00
parent 3ab6a60f8f
commit 21e717d349
4 changed files with 49 additions and 64 deletions

View File

@ -97,7 +97,7 @@ class atf_run : public atf::application::app {
const atf::tests::vars_map&,
const atf::fs::path&);
atf::tests::tcr get_tcr(const atf::process::status&,
atf::tests::tcr get_tcr(const std::string&, const atf::process::status&,
const atf::fs::path&) const;
public:
@ -212,12 +212,16 @@ atf_run::run_test_directory(const atf::fs::path& tp,
}
atf::tests::tcr
atf_run::get_tcr(const atf::process::status& s,
atf_run::get_tcr(const std::string& broken_reason,
const atf::process::status& s,
const atf::fs::path& resfile)
const
{
using atf::tests::tcr;
if (!broken_reason.empty())
return tcr(tcr::failed_state, broken_reason);
if (s.exited()) {
try {
const tcr ret = tcr::read(resfile);
@ -244,14 +248,10 @@ atf_run::get_tcr(const atf::process::status& s,
std::string(e.what()));
}
} else if (s.signaled()) {
try {
return tcr::read(resfile);
} catch (...) {
return tcr(tcr::failed_state,
"Test program received signal " +
atf::text::to_string(s.termsig()) +
(s.coredump() ? " (core dumped)" : ""));
}
return tcr(tcr::failed_state,
"Test program received signal " +
atf::text::to_string(s.termsig()) +
(s.coredump() ? " (core dumped)" : ""));
} else {
UNREACHABLE;
throw std::runtime_error("Unknown exit status");
@ -311,6 +311,7 @@ atf_run::run_test_program(const atf::fs::path& tp,
}
const atf::fs::path resfile = resdir.get_path() / "tcr";
INV(!atf::fs::exists(resfile));
try {
const bool use_fs = atf::text::to_bool(
(*tcmd.find("use.fs")).second);
@ -321,34 +322,35 @@ atf_run::run_test_program(const atf::fs::path& tp,
atf::fs::temp_dir workdir(atf::fs::path(atf::config::get(
"atf_workdir")) / "atf-run.XXXXXX");
const atf::process::status body_status =
std::pair< std::string, const atf::process::status > s =
impl::run_test_case(tp, tcname, "body", tcmd, config,
resfile, workdir.get_path(), w);
const atf::process::status cleanup_status =
impl::run_test_case(tp, tcname, "cleanup", tcmd, config,
resfile, workdir.get_path(), w);
(void)impl::run_test_case(tp, tcname, "cleanup", tcmd, config,
resfile, workdir.get_path(), w);
// TODO: Force deletion of workdir.
tcr = get_tcr(body_status, resfile);
tcr = get_tcr(s.first, s.second, resfile);
} else {
const atf::process::status body_status =
std::pair< std::string, const atf::process::status > s =
impl::run_test_case(tp, tcname, "body", tcmd, config,
resfile, ro_workdir, w);
const atf::process::status cleanup_status =
impl::run_test_case(tp, tcname, "cleanup", tcmd, config,
resfile, ro_workdir, w);
(void)impl::run_test_case(tp, tcname, "cleanup", tcmd, config,
resfile, ro_workdir, w);
tcr = get_tcr(body_status, resfile);
tcr = get_tcr(s.first, s.second, resfile);
}
w.end_tc(tcr);
if (tcr.get_state() == atf::tests::tcr::failed_state)
errcode = EXIT_FAILURE;
} catch (...) {
atf::fs::remove(resfile);
if (atf::fs::exists(resfile))
atf::fs::remove(resfile);
throw;
}
if (atf::fs::exists(resfile))
atf::fs::remove(resfile);
}
w.end_tp("");
}

View File

@ -186,23 +186,6 @@ exec_or_exit(const atf::fs::path& executable,
std::exit(EXIT_FAILURE);
}
static
void
create_timeout_resfile(const atf::fs::path& path, const unsigned int timeout)
{
std::ofstream os(path.c_str());
if (!os)
throw std::runtime_error("Failed to create " + path.str());
const std::string reason = "Test case timed out after " +
atf::text::to_string(timeout) + " " +
(timeout == 1 ? "second" : "seconds");
atf::formats::atf_tcr_writer writer(os);
writer.result("failed");
writer.reason(reason);
}
static
std::vector< std::string >
config_to_args(const atf::tests::vars_map& config)
@ -309,7 +292,7 @@ impl::get_metadata(const atf::fs::path& executable,
return metadata(parser.get_tcs());
}
atf::process::status
std::pair< std::string, atf::process::status >
impl::run_test_case(const atf::fs::path& executable,
const std::string& test_case_name,
const std::string& test_case_part,
@ -357,11 +340,14 @@ impl::run_test_case(const atf::fs::path& executable,
atf::process::status status = child.wait();
::killpg(child_pid, SIGKILL);
std::string reason;
if (timeout_timer.fired()) {
INV(status.signaled());
INV(status.termsig() == SIGKILL);
create_timeout_resfile(resfile, timeout);
reason = "Test case timed out after " + atf::text::to_string(timeout) +
" " + (timeout == 1 ? "second" : "seconds");
}
return status;
return std::make_pair(reason, status);
}

View File

@ -53,14 +53,10 @@ struct metadata {
};
metadata get_metadata(const atf::fs::path&, const atf::tests::vars_map&);
atf::process::status run_test_case(const atf::fs::path&,
const std::string&,
const std::string&,
const atf::tests::vars_map&,
const atf::tests::vars_map&,
const atf::fs::path&,
const atf::fs::path&,
atf::formats::atf_tps_writer&);
std::pair< std::string, atf::process::status > run_test_case(const atf::fs::path&,
const std::string&, const std::string&, const atf::tests::vars_map&,
const atf::tests::vars_map&, const atf::fs::path&, const atf::fs::path&,
atf::formats::atf_tps_writer&);
} // namespace atf_run
} // namespace atf

View File

@ -49,6 +49,7 @@ create_helper()
create_helper_stdin()
{
# TODO: This really, really, really must use real test programs.
cat >${1} <<EOF
#! $(atf-config -t atf_shell)
while [ \${#} -gt 0 ]; do
@ -57,12 +58,11 @@ while [ \${#} -gt 0 ]; do
echo 'Content-Type: application/X-atf-tp; version="1"'
echo
EOF
cnt=${2}
while [ ${cnt} -gt 0 ]; do
cnt=1
while [ ${cnt} -le ${2} ]; do
echo "echo 'ident: tc${cnt}'" >>${1}
cnt=$((${cnt} - 1))
[ ${cnt} -gt 0 ] && echo
[ ${cnt} -lt ${2} ] && echo "echo" >>${1}
cnt=$((${cnt} + 1))
done
cat >>${1} <<EOF
exit 0
@ -71,6 +71,7 @@ cat >>${1} <<EOF
resfile=\$(echo \${1} | cut -d r -f 2-)
;;
esac
testcase=\$(echo \${1} | cut -d : -f 1)
shift
done
EOF
@ -412,20 +413,21 @@ signaled_head()
}
signaled_body()
{
create_helper_stdin helper 1 <<EOF
echo 'Content-Type: application/X-atf-tcs; version="1"' 1>&9
echo 1>&9
echo "tcs-count: 1" 1>&9
echo "tc-start: tc1" 1>&9
echo "tc-end: tc1, failed, Will fail" 1>&9
kill -9 \$\$
create_helper_stdin helper 2 <<EOF
echo 'Content-Type: application/X-atf-tcr; version="1"' >\${resfile}
echo >>\${resfile}
echo "result: passed" >>\${resfile}
case \${testcase} in
tc1) ;;
tc2) echo "Killing myself!" ; kill -9 \$\$ ;;
esac
EOF
chmod +x helper
create_atffile helper
atf_check -s eq:1 -o save:stdout -e empty atf-run
atf_check -s eq:0 -o save:stdout -e empty grep '^tc-end: tc1, ' stdout
atf_check -s eq:0 -o save:stdout -e empty grep '^tc-end: tc2, ' stdout
atf_check -s eq:0 -o ignore -e empty grep 'received signal 9' stdout
}
@ -451,7 +453,6 @@ EOF
create_atffile helper
atf_check -s eq:1 -o save:stdout -e empty atf-run
cat stdout
atf_check -s eq:0 -o ignore -e empty \
grep '^tc-end: tc1,.*No reason specified' stdout
done