yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
test_recorder.cc
Go to the documentation of this file.
2
3#include <utility>
4
5#include "absl/strings/ascii.h"
6#include "absl/strings/str_cat.h"
7#include "absl/strings/str_format.h"
8#include "absl/time/clock.h"
9#include "absl/time/time.h"
12#include "util/macro.h"
13
14namespace yaze {
15namespace test {
16namespace {
17
18constexpr absl::Duration kTestCompletionTimeout = absl::Seconds(10);
19constexpr absl::Duration kPollInterval = absl::Milliseconds(50);
20
21#if defined(YAZE_WITH_GRPC)
22const char* HarnessStatusToString(test::HarnessTestStatus status) {
23 switch (status) {
24 case HarnessTestStatus::kQueued:
25 return "queued";
26 case HarnessTestStatus::kRunning:
27 return "running";
28 case HarnessTestStatus::kPassed:
29 return "passed";
30 case HarnessTestStatus::kFailed:
31 return "failed";
32 case HarnessTestStatus::kTimeout:
33 return "timeout";
34 case HarnessTestStatus::kUnspecified:
35 default:
36 return "unspecified";
37 }
38}
39#endif // defined(YAZE_WITH_GRPC)
40
41} // namespace
42
44 bool active)
45 : recorder_(recorder), active_(active) {}
46
48 if (!recorder_ || !active_) {
49 return;
50 }
51 absl::MutexLock lock(&recorder_->mu_);
52 recorder_->suspended_ = false;
53}
54
56 : test_manager_(test_manager) {}
57
58absl::StatusOr<std::string> TestRecorder::Start(
59 const RecordingOptions& options) {
60 absl::MutexLock lock(&mu_);
61 return StartLocked(options);
62}
63
64absl::StatusOr<TestRecorder::StopRecordingSummary> TestRecorder::Stop(
65 const std::string& recording_id, bool discard) {
66 absl::MutexLock lock(&mu_);
67 return StopLocked(recording_id, discard);
68}
69
71 absl::MutexLock lock(&mu_);
72 RecordStepLocked(step);
73}
74
76 absl::MutexLock lock(&mu_);
77 return recording_ && !suspended_;
78}
79
81 absl::MutexLock lock(&mu_);
82 return recording_id_;
83}
84
86 absl::MutexLock lock(&mu_);
87 bool activate = false;
88 if (!suspended_) {
89 suspended_ = true;
90 activate = true;
91 }
92 return ScopedSuspension(this, activate);
93}
94
95absl::StatusOr<std::string> TestRecorder::StartLocked(
96 const RecordingOptions& options) {
97 if (recording_) {
98 return absl::FailedPreconditionError(
99 "A recording session is already active");
100 }
101 if (!test_manager_) {
102 return absl::FailedPreconditionError("TestManager unavailable");
103 }
104 if (options.output_path.empty()) {
105 return absl::InvalidArgumentError(
106 "Recording requires a non-empty output path");
107 }
108
109 recording_ = true;
110 suspended_ = false;
111 options_ = options;
112 if (options_.session_name.empty()) {
113 options_.session_name = "Untitled Recording";
114 }
115 started_at_ = absl::Now();
116 steps_.clear();
117 recording_id_ = GenerateRecordingId();
118 return recording_id_;
119}
120
121absl::StatusOr<TestRecorder::StopRecordingSummary> TestRecorder::StopLocked(
122 const std::string& recording_id, bool discard) {
123 if (!recording_) {
124 return absl::FailedPreconditionError("No active recording session");
125 }
126 if (!recording_id.empty() && recording_id != recording_id_) {
127 return absl::InvalidArgumentError(
128 absl::StrFormat("Recording ID mismatch (expected %s)", recording_id_));
129 }
130
131 StopRecordingSummary summary;
132 summary.step_count = static_cast<int>(steps_.size());
133 summary.duration = absl::Now() - started_at_;
134 summary.output_path = options_.output_path;
135 summary.saved = !discard;
136
137 if (!discard) {
139 TestScript script;
140 script.recording_id = recording_id_;
141 script.name = options_.session_name;
142 script.description = options_.description;
143 script.created_at = started_at_;
144 script.duration = summary.duration;
145
146 for (const auto& step : steps_) {
147 TestScriptStep script_step;
148 script_step.action = ActionTypeToString(step.type);
149 script_step.target = step.target;
150 script_step.click_type = absl::AsciiStrToLower(step.click_type);
151 script_step.text = step.text;
152 script_step.clear_first = step.clear_first;
153 script_step.condition = step.condition;
154 script_step.timeout_ms = step.timeout_ms;
155 script_step.region = step.region;
156 script_step.format = step.format;
157 script_step.expect_success = step.success;
158#if defined(YAZE_WITH_GRPC)
159 script_step.expect_status =
160 ::yaze::test::HarnessStatusToString(step.final_status);
161#else
162 script_step.expect_status.clear();
163#endif
164 if (!step.final_error_message.empty()) {
165 script_step.expect_message = step.final_error_message;
166 } else {
167 script_step.expect_message = step.message;
168 }
169 script_step.expect_assertion_failures = step.assertion_failures;
170 script_step.expect_metrics = step.metrics;
171 script.steps.push_back(std::move(script_step));
172 }
173
175 TestScriptParser::WriteToFile(script, options_.output_path));
176 }
177
178 // Reset state
179 recording_ = false;
180 suspended_ = false;
181 recording_id_.clear();
182 options_ = RecordingOptions{};
183 started_at_ = absl::InfinitePast();
184 steps_.clear();
185
186 return summary;
187}
188
190 if (!recording_ || suspended_) {
191 return;
192 }
193 RecordedStep copy = step;
194 if (copy.captured_at == absl::InfinitePast()) {
195 copy.captured_at = absl::Now();
196 }
197 steps_.push_back(std::move(copy));
198}
199
201 if (!test_manager_) {
202 return absl::FailedPreconditionError("TestManager unavailable");
203 }
204
205#if !defined(YAZE_WITH_GRPC)
206 return absl::OkStatus();
207#else
208 for (auto& step : steps_) {
209 if (step.test_id.empty()) {
210 continue;
211 }
212
213 const absl::Time deadline = absl::Now() + kTestCompletionTimeout;
214 while (absl::Now() < deadline) {
215 absl::StatusOr<test::HarnessTestExecution> execution =
216 test_manager_->GetHarnessTestExecution(step.test_id);
217 if (!execution.ok()) {
218 absl::SleepFor(kPollInterval);
219 continue;
220 }
221
222 step.final_status = execution->status;
223 step.final_error_message = execution->error_message;
224 step.assertion_failures = execution->assertion_failures;
225 step.metrics = execution->metrics;
226
227 if (execution->status == test::HarnessTestStatus::kQueued ||
228 execution->status == test::HarnessTestStatus::kRunning) {
229 absl::SleepFor(kPollInterval);
230 continue;
231 }
232 break;
233 }
234 }
235
236 return absl::OkStatus();
237#endif // defined(YAZE_WITH_GRPC)
238}
239
241 return absl::StrFormat(
242 "rec_%s",
243 absl::FormatTime("%Y%m%dT%H%M%S", absl::Now(), absl::UTCTimeZone()));
244}
245
247 switch (type) {
249 return "click";
251 return "type";
253 return "wait";
255 return "assert";
257 return "screenshot";
259 default:
260 return "unknown";
261 }
262}
263
264} // namespace test
265} // namespace yaze
ScopedSuspension(TestRecorder *recorder, bool active)
void RecordStepLocked(const RecordedStep &step) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_)
static std::string GenerateRecordingId()
absl::StatusOr< StopRecordingSummary > StopLocked(const std::string &recording_id, bool discard) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_)
absl::Status PopulateFinalStatusLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_)
TestManager *const test_manager_
TestRecorder(TestManager *test_manager)
absl::StatusOr< std::string > Start(const RecordingOptions &options)
absl::StatusOr< StopRecordingSummary > Stop(const std::string &recording_id, bool discard)
ScopedSuspension Suspend()
static const char * ActionTypeToString(ActionType type)
absl::StatusOr< std::string > StartLocked(const RecordingOptions &options) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_)
void RecordStep(const RecordedStep &step)
std::string CurrentRecordingId() const
static absl::Status WriteToFile(const TestScript &script, const std::string &path)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::vector< std::string > expect_assertion_failures
std::map< std::string, int32_t > expect_metrics
std::vector< TestScriptStep > steps