|
1 | 1 | #include "app/student_app.hpp" |
2 | 2 |
|
| 3 | +#include "api/assignment.hpp" |
| 4 | +#include "common/linux.hpp" |
3 | 5 | #include "grading_session.hpp" |
4 | 6 | #include "logging.hpp" |
5 | 7 | #include "output/plaintext_serializer.hpp" |
|
9 | 11 | #include "test_runner.hpp" |
10 | 12 | #include "user/program_options.hpp" |
11 | 13 |
|
| 14 | +#include <fmt/base.h> |
12 | 15 | #include <fmt/format.h> |
13 | 16 | #include <libassert/assert.hpp> |
| 17 | +#include <range/v3/view/transform.hpp> |
14 | 18 |
|
15 | 19 | #include <cstdlib> |
| 20 | +#include <filesystem> |
| 21 | +#include <functional> |
16 | 22 | #include <memory> |
17 | 23 | #include <optional> |
| 24 | +#include <string> |
| 25 | +#include <string_view> |
| 26 | +#include <vector> |
| 27 | + |
| 28 | +#include <unistd.h> |
18 | 29 |
|
19 | 30 | namespace asmgrader { |
20 | 31 |
|
| 32 | +namespace { |
| 33 | + |
| 34 | +bool can_execute(const std::filesystem::path& path) { |
| 35 | + namespace fs = std::filesystem; |
| 36 | + |
| 37 | + if (!fs::is_regular_file(path)) { |
| 38 | + LOG_TRACE("{} is not a regular file", path); |
| 39 | + return false; |
| 40 | + } |
| 41 | + |
| 42 | + if (!linux::access(path.c_str(), X_OK)) { |
| 43 | + LOG_TRACE("{} is not executable by this program", path); |
| 44 | + return false; |
| 45 | + } |
| 46 | + |
| 47 | + return true; |
| 48 | +} |
| 49 | + |
| 50 | +std::reference_wrapper<Assignment> infer_assignment_or_exit() { |
| 51 | + namespace fs = std::filesystem; |
| 52 | + |
| 53 | + auto& registrar = GlobalRegistrar::get(); |
| 54 | + |
| 55 | + std::vector<std::reference_wrapper<Assignment>> infer_res; |
| 56 | + |
| 57 | + for (fs::path cwd = fs::current_path(); const auto& dirent : fs::directory_iterator{cwd}) { |
| 58 | + const auto& path = dirent.path(); |
| 59 | + std::string pathname = path.filename().string(); |
| 60 | + LOG_TRACE("Checking if {:?} matches any assignments {}", pathname, dirent.path()); |
| 61 | + |
| 62 | + if (!can_execute(path)) { |
| 63 | + continue; |
| 64 | + } |
| 65 | + |
| 66 | + if (auto found = registrar.get_assignment_by_pathname(pathname)) { |
| 67 | + LOG_TRACE("{:?} matches an assignment", pathname); |
| 68 | + infer_res.push_back(found.value()); |
| 69 | + } |
| 70 | + } |
| 71 | + |
| 72 | + if (infer_res.size() == 0) { |
| 73 | + fmt::println(stderr, "Could not infer assignment based on files in the current working directory! Please " |
| 74 | + "either change directories or specify the assignment explicitly by name. Exiting."); |
| 75 | + std::exit(1); |
| 76 | + } |
| 77 | + |
| 78 | + if (infer_res.size() > 1) { |
| 79 | + fmt::println( |
| 80 | + stderr, |
| 81 | + "More than 1 valid assignment found in current working directory when attempting to infer " |
| 82 | + "assignment.\nThis is ambiguous! Please specify the assignment explicitly by name.\n(Found: {::?})", |
| 83 | + infer_res | |
| 84 | + ranges::views::transform([](auto& assignment_ref) { return assignment_ref.get().get_exec_path(); })); |
| 85 | + std::exit(1); |
| 86 | + } |
| 87 | + |
| 88 | + // infer_res.size() == 1 |
| 89 | + return infer_res.front(); |
| 90 | +} |
| 91 | + |
| 92 | +} // namespace |
| 93 | + |
| 94 | +Assignment& StudentApp::get_assignment_or_exit() const { |
| 95 | + if (OPTS.assignment_name.empty()) { |
| 96 | + LOG_DEBUG("Assignment unspecified; attempting to infer based on files in cwd."); |
| 97 | + return infer_assignment_or_exit(); |
| 98 | + } |
| 99 | + |
| 100 | + auto opt_assignment = GlobalRegistrar::get().get_assignment(OPTS.assignment_name); |
| 101 | + ASSERT(opt_assignment.has_value(), "Internal error locating assignment", OPTS.assignment_name); |
| 102 | + |
| 103 | + return opt_assignment.value(); |
| 104 | +} |
| 105 | + |
21 | 106 | int StudentApp::run_impl() { |
22 | 107 | LOG_TRACE("Registered tests: {::}", GlobalRegistrar::get().for_each_assignment([](const Assignment& assignment) { |
23 | 108 | return fmt::format("{:?}: {}", assignment.get_name(), assignment.get_test_names()); |
24 | 109 | })); |
25 | 110 |
|
26 | | - auto assignment = GlobalRegistrar::get().get_assignment(OPTS.assignment_name); |
27 | | - |
28 | | - // should be verified by CLI; double-check here just in case |
29 | | - ASSERT(assignment, "Error locating assignment {}", OPTS.assignment_name); |
| 111 | + Assignment& assignment = get_assignment_or_exit(); |
30 | 112 |
|
31 | 113 | StdoutSink output_sink; |
32 | 114 | std::shared_ptr output_serializer = |
33 | 115 | std::make_shared<PlainTextSerializer>(output_sink, OPTS.colorize_option, OPTS.verbosity); |
34 | | - AssignmentTestRunner runner{*assignment, output_serializer, OPTS.tests_filter}; |
| 116 | + AssignmentTestRunner runner{assignment, output_serializer, OPTS.tests_filter}; |
35 | 117 |
|
36 | 118 | output_serializer->on_run_metadata(RunMetadata{}); |
37 | 119 | AssignmentResult res = runner.run_all(OPTS.file_name); |
|
0 commit comments