1*b0d29bc4SBrooks Davis // Copyright 2011 The Kyua Authors.
2*b0d29bc4SBrooks Davis // All rights reserved.
3*b0d29bc4SBrooks Davis //
4*b0d29bc4SBrooks Davis // Redistribution and use in source and binary forms, with or without
5*b0d29bc4SBrooks Davis // modification, are permitted provided that the following conditions are
6*b0d29bc4SBrooks Davis // met:
7*b0d29bc4SBrooks Davis //
8*b0d29bc4SBrooks Davis // * Redistributions of source code must retain the above copyright
9*b0d29bc4SBrooks Davis // notice, this list of conditions and the following disclaimer.
10*b0d29bc4SBrooks Davis // * Redistributions in binary form must reproduce the above copyright
11*b0d29bc4SBrooks Davis // notice, this list of conditions and the following disclaimer in the
12*b0d29bc4SBrooks Davis // documentation and/or other materials provided with the distribution.
13*b0d29bc4SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors
14*b0d29bc4SBrooks Davis // may be used to endorse or promote products derived from this software
15*b0d29bc4SBrooks Davis // without specific prior written permission.
16*b0d29bc4SBrooks Davis //
17*b0d29bc4SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18*b0d29bc4SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19*b0d29bc4SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20*b0d29bc4SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21*b0d29bc4SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22*b0d29bc4SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23*b0d29bc4SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24*b0d29bc4SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25*b0d29bc4SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26*b0d29bc4SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27*b0d29bc4SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28*b0d29bc4SBrooks Davis
29*b0d29bc4SBrooks Davis #include "drivers/run_tests.hpp"
30*b0d29bc4SBrooks Davis
31*b0d29bc4SBrooks Davis #include <utility>
32*b0d29bc4SBrooks Davis
33*b0d29bc4SBrooks Davis #include "engine/config.hpp"
34*b0d29bc4SBrooks Davis #include "engine/filters.hpp"
35*b0d29bc4SBrooks Davis #include "engine/kyuafile.hpp"
36*b0d29bc4SBrooks Davis #include "engine/scanner.hpp"
37*b0d29bc4SBrooks Davis #include "engine/scheduler.hpp"
38*b0d29bc4SBrooks Davis #include "model/context.hpp"
39*b0d29bc4SBrooks Davis #include "model/metadata.hpp"
40*b0d29bc4SBrooks Davis #include "model/test_case.hpp"
41*b0d29bc4SBrooks Davis #include "model/test_program.hpp"
42*b0d29bc4SBrooks Davis #include "model/test_result.hpp"
43*b0d29bc4SBrooks Davis #include "store/write_backend.hpp"
44*b0d29bc4SBrooks Davis #include "store/write_transaction.hpp"
45*b0d29bc4SBrooks Davis #include "utils/config/tree.ipp"
46*b0d29bc4SBrooks Davis #include "utils/datetime.hpp"
47*b0d29bc4SBrooks Davis #include "utils/defs.hpp"
48*b0d29bc4SBrooks Davis #include "utils/format/macros.hpp"
49*b0d29bc4SBrooks Davis #include "utils/logging/macros.hpp"
50*b0d29bc4SBrooks Davis #include "utils/noncopyable.hpp"
51*b0d29bc4SBrooks Davis #include "utils/optional.ipp"
52*b0d29bc4SBrooks Davis #include "utils/passwd.hpp"
53*b0d29bc4SBrooks Davis #include "utils/text/operations.ipp"
54*b0d29bc4SBrooks Davis
55*b0d29bc4SBrooks Davis namespace config = utils::config;
56*b0d29bc4SBrooks Davis namespace datetime = utils::datetime;
57*b0d29bc4SBrooks Davis namespace fs = utils::fs;
58*b0d29bc4SBrooks Davis namespace passwd = utils::passwd;
59*b0d29bc4SBrooks Davis namespace scheduler = engine::scheduler;
60*b0d29bc4SBrooks Davis namespace text = utils::text;
61*b0d29bc4SBrooks Davis
62*b0d29bc4SBrooks Davis using utils::none;
63*b0d29bc4SBrooks Davis using utils::optional;
64*b0d29bc4SBrooks Davis
65*b0d29bc4SBrooks Davis
66*b0d29bc4SBrooks Davis namespace {
67*b0d29bc4SBrooks Davis
68*b0d29bc4SBrooks Davis
69*b0d29bc4SBrooks Davis /// Map of test program identifiers (relative paths) to their identifiers in the
70*b0d29bc4SBrooks Davis /// database. We need to keep this in memory because test programs can be
71*b0d29bc4SBrooks Davis /// returned by the scanner in any order, and we only want to put each test
72*b0d29bc4SBrooks Davis /// program once.
73*b0d29bc4SBrooks Davis typedef std::map< fs::path, int64_t > path_to_id_map;
74*b0d29bc4SBrooks Davis
75*b0d29bc4SBrooks Davis
76*b0d29bc4SBrooks Davis /// Map of in-flight PIDs to their corresponding test case IDs.
77*b0d29bc4SBrooks Davis typedef std::map< int, int64_t > pid_to_id_map;
78*b0d29bc4SBrooks Davis
79*b0d29bc4SBrooks Davis
80*b0d29bc4SBrooks Davis /// Pair of PID to a test case ID.
81*b0d29bc4SBrooks Davis typedef pid_to_id_map::value_type pid_and_id_pair;
82*b0d29bc4SBrooks Davis
83*b0d29bc4SBrooks Davis
84*b0d29bc4SBrooks Davis /// Puts a test program in the store and returns its identifier.
85*b0d29bc4SBrooks Davis ///
86*b0d29bc4SBrooks Davis /// This function is idempotent: we maintain a side cache of already-put test
87*b0d29bc4SBrooks Davis /// programs so that we can return their identifiers without having to put them
88*b0d29bc4SBrooks Davis /// again.
89*b0d29bc4SBrooks Davis /// TODO(jmmv): It's possible that the store module should offer this
90*b0d29bc4SBrooks Davis /// functionality and not have to do this ourselves here.
91*b0d29bc4SBrooks Davis ///
92*b0d29bc4SBrooks Davis /// \param test_program The test program being put.
93*b0d29bc4SBrooks Davis /// \param [in,out] tx Writable transaction on the store.
94*b0d29bc4SBrooks Davis /// \param [in,out] ids_cache Cache of already-put test programs.
95*b0d29bc4SBrooks Davis ///
96*b0d29bc4SBrooks Davis /// \return A test program identifier.
97*b0d29bc4SBrooks Davis static int64_t
find_test_program_id(const model::test_program_ptr test_program,store::write_transaction & tx,path_to_id_map & ids_cache)98*b0d29bc4SBrooks Davis find_test_program_id(const model::test_program_ptr test_program,
99*b0d29bc4SBrooks Davis store::write_transaction& tx,
100*b0d29bc4SBrooks Davis path_to_id_map& ids_cache)
101*b0d29bc4SBrooks Davis {
102*b0d29bc4SBrooks Davis const fs::path& key = test_program->relative_path();
103*b0d29bc4SBrooks Davis std::map< fs::path, int64_t >::const_iterator iter = ids_cache.find(key);
104*b0d29bc4SBrooks Davis if (iter == ids_cache.end()) {
105*b0d29bc4SBrooks Davis const int64_t id = tx.put_test_program(*test_program);
106*b0d29bc4SBrooks Davis ids_cache.insert(std::make_pair(key, id));
107*b0d29bc4SBrooks Davis return id;
108*b0d29bc4SBrooks Davis } else {
109*b0d29bc4SBrooks Davis return (*iter).second;
110*b0d29bc4SBrooks Davis }
111*b0d29bc4SBrooks Davis }
112*b0d29bc4SBrooks Davis
113*b0d29bc4SBrooks Davis
114*b0d29bc4SBrooks Davis /// Stores the result of an execution in the database.
115*b0d29bc4SBrooks Davis ///
116*b0d29bc4SBrooks Davis /// \param test_case_id Identifier of the test case in the database.
117*b0d29bc4SBrooks Davis /// \param result The result of the execution.
118*b0d29bc4SBrooks Davis /// \param [in,out] tx Writable transaction where to store the result data.
119*b0d29bc4SBrooks Davis static void
put_test_result(const int64_t test_case_id,const scheduler::test_result_handle & result,store::write_transaction & tx)120*b0d29bc4SBrooks Davis put_test_result(const int64_t test_case_id,
121*b0d29bc4SBrooks Davis const scheduler::test_result_handle& result,
122*b0d29bc4SBrooks Davis store::write_transaction& tx)
123*b0d29bc4SBrooks Davis {
124*b0d29bc4SBrooks Davis tx.put_result(result.test_result(), test_case_id,
125*b0d29bc4SBrooks Davis result.start_time(), result.end_time());
126*b0d29bc4SBrooks Davis tx.put_test_case_file("__STDOUT__", result.stdout_file(), test_case_id);
127*b0d29bc4SBrooks Davis tx.put_test_case_file("__STDERR__", result.stderr_file(), test_case_id);
128*b0d29bc4SBrooks Davis
129*b0d29bc4SBrooks Davis }
130*b0d29bc4SBrooks Davis
131*b0d29bc4SBrooks Davis
132*b0d29bc4SBrooks Davis /// Cleans up a test case and folds any errors into the test result.
133*b0d29bc4SBrooks Davis ///
134*b0d29bc4SBrooks Davis /// \param handle The result handle for the test.
135*b0d29bc4SBrooks Davis ///
136*b0d29bc4SBrooks Davis /// \return The test result if the cleanup succeeds; a broken test result
137*b0d29bc4SBrooks Davis /// otherwise.
138*b0d29bc4SBrooks Davis model::test_result
safe_cleanup(scheduler::test_result_handle handle)139*b0d29bc4SBrooks Davis safe_cleanup(scheduler::test_result_handle handle) throw()
140*b0d29bc4SBrooks Davis {
141*b0d29bc4SBrooks Davis try {
142*b0d29bc4SBrooks Davis handle.cleanup();
143*b0d29bc4SBrooks Davis return handle.test_result();
144*b0d29bc4SBrooks Davis } catch (const std::exception& e) {
145*b0d29bc4SBrooks Davis return model::test_result(
146*b0d29bc4SBrooks Davis model::test_result_broken,
147*b0d29bc4SBrooks Davis F("Failed to clean up test case's work directory %s: %s") %
148*b0d29bc4SBrooks Davis handle.work_directory() % e.what());
149*b0d29bc4SBrooks Davis }
150*b0d29bc4SBrooks Davis }
151*b0d29bc4SBrooks Davis
152*b0d29bc4SBrooks Davis
153*b0d29bc4SBrooks Davis /// Starts a test asynchronously.
154*b0d29bc4SBrooks Davis ///
155*b0d29bc4SBrooks Davis /// \param handle Scheduler handle.
156*b0d29bc4SBrooks Davis /// \param match Test program and test case to start.
157*b0d29bc4SBrooks Davis /// \param [in,out] tx Writable transaction to obtain test IDs.
158*b0d29bc4SBrooks Davis /// \param [in,out] ids_cache Cache of already-put test cases.
159*b0d29bc4SBrooks Davis /// \param user_config The end-user configuration properties.
160*b0d29bc4SBrooks Davis /// \param hooks The hooks for this execution.
161*b0d29bc4SBrooks Davis ///
162*b0d29bc4SBrooks Davis /// \returns The PID for the started test and the test case's identifier in the
163*b0d29bc4SBrooks Davis /// store.
164*b0d29bc4SBrooks Davis pid_and_id_pair
start_test(scheduler::scheduler_handle & handle,const engine::scan_result & match,store::write_transaction & tx,path_to_id_map & ids_cache,const config::tree & user_config,drivers::run_tests::base_hooks & hooks)165*b0d29bc4SBrooks Davis start_test(scheduler::scheduler_handle& handle,
166*b0d29bc4SBrooks Davis const engine::scan_result& match,
167*b0d29bc4SBrooks Davis store::write_transaction& tx,
168*b0d29bc4SBrooks Davis path_to_id_map& ids_cache,
169*b0d29bc4SBrooks Davis const config::tree& user_config,
170*b0d29bc4SBrooks Davis drivers::run_tests::base_hooks& hooks)
171*b0d29bc4SBrooks Davis {
172*b0d29bc4SBrooks Davis const model::test_program_ptr test_program = match.first;
173*b0d29bc4SBrooks Davis const std::string& test_case_name = match.second;
174*b0d29bc4SBrooks Davis
175*b0d29bc4SBrooks Davis hooks.got_test_case(*test_program, test_case_name);
176*b0d29bc4SBrooks Davis
177*b0d29bc4SBrooks Davis const int64_t test_program_id = find_test_program_id(
178*b0d29bc4SBrooks Davis test_program, tx, ids_cache);
179*b0d29bc4SBrooks Davis const int64_t test_case_id = tx.put_test_case(
180*b0d29bc4SBrooks Davis *test_program, test_case_name, test_program_id);
181*b0d29bc4SBrooks Davis
182*b0d29bc4SBrooks Davis const scheduler::exec_handle exec_handle = handle.spawn_test(
183*b0d29bc4SBrooks Davis test_program, test_case_name, user_config);
184*b0d29bc4SBrooks Davis return std::make_pair(exec_handle, test_case_id);
185*b0d29bc4SBrooks Davis }
186*b0d29bc4SBrooks Davis
187*b0d29bc4SBrooks Davis
188*b0d29bc4SBrooks Davis /// Processes the completion of a test.
189*b0d29bc4SBrooks Davis ///
190*b0d29bc4SBrooks Davis /// \param [in,out] result_handle The completion handle of the test subprocess.
191*b0d29bc4SBrooks Davis /// \param test_case_id Identifier of the test case as returned by start_test().
192*b0d29bc4SBrooks Davis /// \param [in,out] tx Writable transaction to put the test results.
193*b0d29bc4SBrooks Davis /// \param hooks The hooks for this execution.
194*b0d29bc4SBrooks Davis ///
195*b0d29bc4SBrooks Davis /// \post result_handle is cleaned up. The caller cannot clean it up again.
196*b0d29bc4SBrooks Davis void
finish_test(scheduler::result_handle_ptr result_handle,const int64_t test_case_id,store::write_transaction & tx,drivers::run_tests::base_hooks & hooks)197*b0d29bc4SBrooks Davis finish_test(scheduler::result_handle_ptr result_handle,
198*b0d29bc4SBrooks Davis const int64_t test_case_id,
199*b0d29bc4SBrooks Davis store::write_transaction& tx,
200*b0d29bc4SBrooks Davis drivers::run_tests::base_hooks& hooks)
201*b0d29bc4SBrooks Davis {
202*b0d29bc4SBrooks Davis const scheduler::test_result_handle* test_result_handle =
203*b0d29bc4SBrooks Davis dynamic_cast< const scheduler::test_result_handle* >(
204*b0d29bc4SBrooks Davis result_handle.get());
205*b0d29bc4SBrooks Davis
206*b0d29bc4SBrooks Davis put_test_result(test_case_id, *test_result_handle, tx);
207*b0d29bc4SBrooks Davis
208*b0d29bc4SBrooks Davis const model::test_result test_result = safe_cleanup(*test_result_handle);
209*b0d29bc4SBrooks Davis hooks.got_result(
210*b0d29bc4SBrooks Davis *test_result_handle->test_program(),
211*b0d29bc4SBrooks Davis test_result_handle->test_case_name(),
212*b0d29bc4SBrooks Davis test_result_handle->test_result(),
213*b0d29bc4SBrooks Davis result_handle->end_time() - result_handle->start_time());
214*b0d29bc4SBrooks Davis }
215*b0d29bc4SBrooks Davis
216*b0d29bc4SBrooks Davis
217*b0d29bc4SBrooks Davis /// Extracts the keys of a pid_to_id_map and returns them as a string.
218*b0d29bc4SBrooks Davis ///
219*b0d29bc4SBrooks Davis /// \param map The PID to test ID map from which to get the PIDs.
220*b0d29bc4SBrooks Davis ///
221*b0d29bc4SBrooks Davis /// \return A user-facing string with the collection of PIDs.
222*b0d29bc4SBrooks Davis static std::string
format_pids(const pid_to_id_map & map)223*b0d29bc4SBrooks Davis format_pids(const pid_to_id_map& map)
224*b0d29bc4SBrooks Davis {
225*b0d29bc4SBrooks Davis std::set< pid_to_id_map::key_type > pids;
226*b0d29bc4SBrooks Davis for (pid_to_id_map::const_iterator iter = map.begin(); iter != map.end();
227*b0d29bc4SBrooks Davis ++iter) {
228*b0d29bc4SBrooks Davis pids.insert(iter->first);
229*b0d29bc4SBrooks Davis }
230*b0d29bc4SBrooks Davis return text::join(pids, ",");
231*b0d29bc4SBrooks Davis }
232*b0d29bc4SBrooks Davis
233*b0d29bc4SBrooks Davis
234*b0d29bc4SBrooks Davis } // anonymous namespace
235*b0d29bc4SBrooks Davis
236*b0d29bc4SBrooks Davis
237*b0d29bc4SBrooks Davis /// Pure abstract destructor.
~base_hooks(void)238*b0d29bc4SBrooks Davis drivers::run_tests::base_hooks::~base_hooks(void)
239*b0d29bc4SBrooks Davis {
240*b0d29bc4SBrooks Davis }
241*b0d29bc4SBrooks Davis
242*b0d29bc4SBrooks Davis
243*b0d29bc4SBrooks Davis /// Executes the operation.
244*b0d29bc4SBrooks Davis ///
245*b0d29bc4SBrooks Davis /// \param kyuafile_path The path to the Kyuafile to be loaded.
246*b0d29bc4SBrooks Davis /// \param build_root If not none, path to the built test programs.
247*b0d29bc4SBrooks Davis /// \param store_path The path to the store to be used.
248*b0d29bc4SBrooks Davis /// \param filters The test case filters as provided by the user.
249*b0d29bc4SBrooks Davis /// \param user_config The end-user configuration properties.
250*b0d29bc4SBrooks Davis /// \param hooks The hooks for this execution.
251*b0d29bc4SBrooks Davis ///
252*b0d29bc4SBrooks Davis /// \returns A structure with all results computed by this driver.
253*b0d29bc4SBrooks Davis drivers::run_tests::result
drive(const fs::path & kyuafile_path,const optional<fs::path> build_root,const fs::path & store_path,const std::set<engine::test_filter> & filters,const config::tree & user_config,base_hooks & hooks)254*b0d29bc4SBrooks Davis drivers::run_tests::drive(const fs::path& kyuafile_path,
255*b0d29bc4SBrooks Davis const optional< fs::path > build_root,
256*b0d29bc4SBrooks Davis const fs::path& store_path,
257*b0d29bc4SBrooks Davis const std::set< engine::test_filter >& filters,
258*b0d29bc4SBrooks Davis const config::tree& user_config,
259*b0d29bc4SBrooks Davis base_hooks& hooks)
260*b0d29bc4SBrooks Davis {
261*b0d29bc4SBrooks Davis scheduler::scheduler_handle handle = scheduler::setup();
262*b0d29bc4SBrooks Davis
263*b0d29bc4SBrooks Davis const engine::kyuafile kyuafile = engine::kyuafile::load(
264*b0d29bc4SBrooks Davis kyuafile_path, build_root, user_config, handle);
265*b0d29bc4SBrooks Davis store::write_backend db = store::write_backend::open_rw(store_path);
266*b0d29bc4SBrooks Davis store::write_transaction tx = db.start_write();
267*b0d29bc4SBrooks Davis
268*b0d29bc4SBrooks Davis {
269*b0d29bc4SBrooks Davis const model::context context = scheduler::current_context();
270*b0d29bc4SBrooks Davis (void)tx.put_context(context);
271*b0d29bc4SBrooks Davis }
272*b0d29bc4SBrooks Davis
273*b0d29bc4SBrooks Davis engine::scanner scanner(kyuafile.test_programs(), filters);
274*b0d29bc4SBrooks Davis
275*b0d29bc4SBrooks Davis path_to_id_map ids_cache;
276*b0d29bc4SBrooks Davis pid_to_id_map in_flight;
277*b0d29bc4SBrooks Davis std::vector< engine::scan_result > exclusive_tests;
278*b0d29bc4SBrooks Davis
279*b0d29bc4SBrooks Davis const std::size_t slots = user_config.lookup< config::positive_int_node >(
280*b0d29bc4SBrooks Davis "parallelism");
281*b0d29bc4SBrooks Davis INV(slots >= 1);
282*b0d29bc4SBrooks Davis do {
283*b0d29bc4SBrooks Davis INV(in_flight.size() <= slots);
284*b0d29bc4SBrooks Davis
285*b0d29bc4SBrooks Davis // Spawn as many jobs as needed to fill our execution slots. We do this
286*b0d29bc4SBrooks Davis // first with the assumption that the spawning is faster than any single
287*b0d29bc4SBrooks Davis // job, so we want to keep as many jobs in the background as possible.
288*b0d29bc4SBrooks Davis while (in_flight.size() < slots) {
289*b0d29bc4SBrooks Davis optional< engine::scan_result > match = scanner.yield();
290*b0d29bc4SBrooks Davis if (!match)
291*b0d29bc4SBrooks Davis break;
292*b0d29bc4SBrooks Davis const model::test_program_ptr test_program = match.get().first;
293*b0d29bc4SBrooks Davis const std::string& test_case_name = match.get().second;
294*b0d29bc4SBrooks Davis
295*b0d29bc4SBrooks Davis const model::test_case& test_case = test_program->find(
296*b0d29bc4SBrooks Davis test_case_name);
297*b0d29bc4SBrooks Davis if (test_case.get_metadata().is_exclusive()) {
298*b0d29bc4SBrooks Davis // Exclusive tests get processed later, separately.
299*b0d29bc4SBrooks Davis exclusive_tests.push_back(match.get());
300*b0d29bc4SBrooks Davis continue;
301*b0d29bc4SBrooks Davis }
302*b0d29bc4SBrooks Davis
303*b0d29bc4SBrooks Davis const pid_and_id_pair pid_id = start_test(
304*b0d29bc4SBrooks Davis handle, match.get(), tx, ids_cache, user_config, hooks);
305*b0d29bc4SBrooks Davis INV_MSG(in_flight.find(pid_id.first) == in_flight.end(),
306*b0d29bc4SBrooks Davis F("Spawned test has PID of still-tracked process %s") %
307*b0d29bc4SBrooks Davis pid_id.first);
308*b0d29bc4SBrooks Davis in_flight.insert(pid_id);
309*b0d29bc4SBrooks Davis }
310*b0d29bc4SBrooks Davis
311*b0d29bc4SBrooks Davis // If there are any used slots, consume any at random and return the
312*b0d29bc4SBrooks Davis // result. We consume slots one at a time to give preference to the
313*b0d29bc4SBrooks Davis // spawning of new tests as detailed above.
314*b0d29bc4SBrooks Davis if (!in_flight.empty()) {
315*b0d29bc4SBrooks Davis scheduler::result_handle_ptr result_handle = handle.wait_any();
316*b0d29bc4SBrooks Davis
317*b0d29bc4SBrooks Davis const pid_to_id_map::iterator iter = in_flight.find(
318*b0d29bc4SBrooks Davis result_handle->original_pid());
319*b0d29bc4SBrooks Davis INV_MSG(iter != in_flight.end(),
320*b0d29bc4SBrooks Davis F("Lost track of in-flight PID %s; tracking %s") %
321*b0d29bc4SBrooks Davis result_handle->original_pid() % format_pids(in_flight));
322*b0d29bc4SBrooks Davis const int64_t test_case_id = (*iter).second;
323*b0d29bc4SBrooks Davis in_flight.erase(iter);
324*b0d29bc4SBrooks Davis
325*b0d29bc4SBrooks Davis finish_test(result_handle, test_case_id, tx, hooks);
326*b0d29bc4SBrooks Davis }
327*b0d29bc4SBrooks Davis } while (!in_flight.empty() || !scanner.done());
328*b0d29bc4SBrooks Davis
329*b0d29bc4SBrooks Davis // Run any exclusive tests that we spotted earlier sequentially.
330*b0d29bc4SBrooks Davis for (std::vector< engine::scan_result >::const_iterator
331*b0d29bc4SBrooks Davis iter = exclusive_tests.begin(); iter != exclusive_tests.end();
332*b0d29bc4SBrooks Davis ++iter) {
333*b0d29bc4SBrooks Davis const pid_and_id_pair data = start_test(
334*b0d29bc4SBrooks Davis handle, *iter, tx, ids_cache, user_config, hooks);
335*b0d29bc4SBrooks Davis scheduler::result_handle_ptr result_handle = handle.wait_any();
336*b0d29bc4SBrooks Davis finish_test(result_handle, data.second, tx, hooks);
337*b0d29bc4SBrooks Davis }
338*b0d29bc4SBrooks Davis
339*b0d29bc4SBrooks Davis tx.commit();
340*b0d29bc4SBrooks Davis
341*b0d29bc4SBrooks Davis handle.cleanup();
342*b0d29bc4SBrooks Davis
343*b0d29bc4SBrooks Davis return result(scanner.unused_filters());
344*b0d29bc4SBrooks Davis }
345