| // -*- Mode: C++ -*- |
| // |
| |
| /// @file |
| /// |
| /// This file implements the common functionality for the tests in |
| /// CTF and DWARF readers, it does the abstraction in the `act` test |
| /// stage. |
| |
| #include <fstream> |
| #include <cstring> |
| #include "test-read-common.h" |
| |
| using std::ofstream; |
| using std::cerr; |
| using std::dynamic_pointer_cast; |
| |
| using abigail::tools_utils::emit_prefix; |
| using abigail::tests::get_build_dir; |
| using abigail::xml_writer::write_context_sptr; |
| using abigail::xml_writer::create_write_context; |
| using abigail::xml_writer::write_corpus; |
| |
| namespace abigail |
| { |
| namespace tests |
| { |
| namespace read_common |
| { |
| |
| /// Constructor. |
| /// |
| /// Task to be executed for each test entry in @ref |
| /// abigail::tests::read_common::InOutSpec. |
| /// |
| /// @param InOutSpec the set of tests. |
| /// |
| /// @param a_out_abi_base the output base directory for abixml files. |
| /// |
| /// @param a_in_elf_base the input base directory for object files. |
| /// |
| /// @param a_in_elf_base the input base directory for expected |
| /// abixml files. |
| test_task::test_task(const InOutSpec &s, |
| string& a_out_abi_base, |
| string& a_in_elf_base, |
| string& a_in_abi_base) |
| : is_ok(true), |
| spec(s), |
| out_abi_base(a_out_abi_base), |
| in_elf_base(a_in_elf_base), |
| in_abi_base(a_in_abi_base) |
| {} |
| |
| /// Serialize the abixml @p out_abi_path file. |
| /// |
| /// @param out_abi_path the abixml path file. |
| /// |
| /// @param corp the ABI @ref abigail::ir::corpus. |
| /// |
| /// @return true if abixml file was serialized successfully. Otherwise |
| /// `error_message` is set with @p out_abi_path and false is returned. |
| bool |
| test_task::serialize_corpus(const string& out_abi_path, |
| corpus_sptr corp) |
| { |
| ofstream of(out_abi_path.c_str(), std::ios_base::trunc); |
| if (!of.is_open()) |
| { |
| error_message = string("failed to read ") + out_abi_path + "\n"; |
| return false; |
| } |
| |
| write_context_sptr write_ctxt |
| = create_write_context(corp->get_environment(), of); |
| set_type_id_style(*write_ctxt, spec.type_id_style); |
| is_ok = write_corpus(*write_ctxt, corp, /*indent=*/0); |
| of.close(); |
| |
| return is_ok; |
| } |
| |
| /// Spawn `abidw --abidiff` tool appending @p extargs argument. |
| /// |
| /// Thew input file object used by `abidw` will be specified by |
| /// `in_elf_path'. |
| /// |
| /// @param extargs the extra argument(s) passed to `abidw` tool. |
| /// |
| /// @return true if `abidw` tool was executed correctly. Otherwise |
| /// `error_message` shows the full path of the input file object and |
| /// the full output path for the abixml file. |
| bool |
| test_task::run_abidw(const string& extargs) |
| { |
| string abidw = string(get_build_dir()) + "/tools/abidw"; |
| string drop_private_types; |
| if (!in_public_headers_path.empty()) |
| drop_private_types += "--headers-dir " + in_public_headers_path + |
| " --drop-private-types"; |
| string cmd = abidw + " " + drop_private_types + " --abidiff " + extargs + |
| in_elf_path; |
| if (system(cmd.c_str())) |
| { |
| error_message = string("ABIs differ:\n") |
| + in_elf_path |
| + "\nand:\n" |
| + out_abi_path |
| + "\n"; |
| |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /// Spawn external `diff` command. |
| /// |
| /// The files to be compared are: abixml generated by the input |
| /// object file and the expected abixml file stored in `in_abi_path`. |
| /// |
| /// @return true if `diff` command didn't find defences. Otherwise |
| /// `error_message` shows the full path of the input file object and |
| /// the full output path for the abixml file. |
| bool |
| test_task::run_diff() |
| { |
| set_in_abi_path(); |
| string cmd = "diff -u " + in_abi_path + " " + out_abi_path; |
| if (system(cmd.c_str())) |
| { |
| error_message = string("ABIs differ:\n") |
| + in_abi_path |
| + "\nand:\n" |
| + out_abi_path |
| + "\n"; |
| |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /// Write the usage message to @p out stream object. |
| /// |
| /// @param prog_name the program name. |
| /// |
| /// @param out the stream object to which want to write. |
| void |
| display_usage(const string& prog_name, ostream& out) |
| { |
| emit_prefix(prog_name, out) |
| << "usage: " << prog_name << " [options]\n" |
| << " where options can be: \n" |
| << " --help|-h display this message\n" |
| << " --no-parallel execute testsuite is a sigle thread\n" |
| ; |
| } |
| |
| /// Parse and process test options. |
| /// |
| /// @param argc the arguments number. |
| /// |
| /// @param argv the pointer to the arguments. |
| /// |
| /// @param opts the valid @ref options to be processed/parsed. |
| /// |
| /// @return true if options was processed/parsed successfully. It returns |
| /// false when help is requested or an invalid option is supplied. |
| bool |
| parse_command_line(int argc, char* argv[], options& opts) |
| { |
| for (int i = 1; i < argc; ++i) |
| { |
| if (!strcmp(argv[i], "--no-parallel")) |
| opts.parallel = false; |
| else if (!strcmp(argv[i], "--help") |
| || !strcmp(argv[i], "--h")) |
| return false; |
| else |
| { |
| if (strlen(argv[i]) >= 2 && argv[i][0] == '-' && argv[i][1] == '-') |
| opts.wrong_option = argv[i]; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /// The main entry point to execute the testsuite. |
| /// |
| /// @param num_tests the number of tests to be executed. |
| /// |
| /// @param specs the @ref abigail::tests::read_common::InOutSpec |
| /// tests container. |
| /// |
| /// @param opts the test execution @ref abigail::tests::read_common::options. |
| /// |
| /// @param new_test the @ref create_new_test callback function to create |
| /// a new test task object. |
| /// |
| /// @return true if `all` tests were performed successfully. Otherwise |
| /// false is returned. |
| bool |
| run_tests(const size_t num_tests, const InOutSpec* specs, |
| const options& opts, create_new_test new_test) |
| { |
| size_t num_workers = (opts.parallel |
| ? std::min(abigail::workers::get_number_of_threads(), |
| num_tests) |
| : 1); |
| |
| // Create a task queue. The max number of worker threads of the |
| // queue is the number of the concurrent threads supported by the |
| // processor of the machine this code runs on. But if |
| // --no-parallel was provided then the number of worker threads |
| // equals 1. |
| abigail::workers::queue task_queue(num_workers); |
| bool is_ok = true; |
| |
| string out_abi_base = string(get_build_dir()) + "/tests/"; |
| string in_elf_base = string(abigail::tests::get_src_dir()) + "/tests/"; |
| string in_abi_base = in_elf_base; |
| |
| for (const InOutSpec *s = specs; s->in_elf_path; ++s) |
| { |
| test_task_sptr t(new_test(s, out_abi_base, |
| in_elf_base, |
| in_abi_base)); |
| ABG_ASSERT(task_queue.schedule_task(t)); |
| } |
| |
| // Wait for all worker threads to finish their job, and wind down. |
| task_queue.wait_for_workers_to_complete(); |
| |
| // Now walk the results and print whatever error messages need to be |
| // printed. |
| |
| const vector<abigail::workers::task_sptr>& completed_tasks = |
| task_queue.get_completed_tasks(); |
| |
| ABG_ASSERT(completed_tasks.size() == num_tests); |
| |
| for (vector<abigail::workers::task_sptr>::const_iterator ti = |
| completed_tasks.begin(); |
| ti != completed_tasks.end(); |
| ++ti) |
| { |
| test_task_sptr t = dynamic_pointer_cast<test_task>(*ti); |
| if (!t->is_ok) |
| { |
| is_ok = false; |
| if (!t->error_message.empty()) |
| cerr << t->error_message << '\n'; |
| } |
| } |
| |
| return !is_ok; |
| } |
| |
| }//end namespace read_common |
| }//end namespace tests |
| }//end namespace abigail |