ruby_raider 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/e2e_tests.yml +3 -0
- data/README.md +52 -53
- data/RELEASE.md +14 -1
- data/lib/commands/schedule_commands.rb +84 -0
- data/lib/generators/templates/common/git_ignore.tt +1 -0
- data/lib/ruby_raider/runtime/retry_reset.rb +32 -0
- data/lib/ruby_raider/runtime/rspec.rb +1 -0
- data/lib/ruby_raider/runtime/show_browser.rb +53 -0
- data/lib/ruby_raider/runtime.rb +2 -0
- data/lib/ruby_raider.rb +22 -7
- data/lib/utilities/schedules/adapter.rb +22 -0
- data/lib/utilities/schedules/base.rb +27 -0
- data/lib/utilities/schedules/kind.rb +74 -0
- data/lib/utilities/schedules/legacy.rb +196 -0
- data/lib/utilities/schedules/legacy_stub.rb +62 -0
- data/lib/utilities/{flows → schedules}/next_run.rb +2 -2
- data/lib/utilities/{flows → schedules}/notifier.rb +6 -6
- data/lib/utilities/{flows → schedules}/os/command_runner.rb +1 -1
- data/lib/utilities/{flows → schedules}/os/cron.rb +27 -17
- data/lib/utilities/{flows → schedules}/os/launchd.rb +32 -14
- data/lib/utilities/{flows → schedules}/os/linux.rb +23 -5
- data/lib/utilities/{flows → schedules}/os/systemd.rb +33 -17
- data/lib/utilities/{flows → schedules}/os/task_scheduler.rb +36 -20
- data/lib/utilities/schedules/os_enumerator.rb +30 -0
- data/lib/utilities/schedules/project.rb +70 -0
- data/lib/utilities/schedules/rule.rb +162 -0
- data/lib/utilities/schedules/run_lock.rb +160 -0
- data/lib/utilities/schedules/run_log.rb +122 -0
- data/lib/utilities/schedules/run_report.rb +139 -0
- data/lib/utilities/schedules/run_wrapper.rb +216 -0
- data/lib/utilities/schedules/schedule.rb +89 -0
- data/lib/utilities/schedules/schedule_store.rb +53 -0
- data/lib/utilities/schedules/scheduler.rb +178 -0
- data/lib/utilities/schedules/scheduler_steps.rb +69 -0
- data/lib/utilities/schedules/selection.rb +133 -0
- data/lib/utilities/schedules/spawn.rb +68 -0
- data/lib/utilities/{flows/schedule_store.rb → schedules/store.rb} +17 -13
- data/lib/version +1 -1
- data/ruby_raider.gemspec +1 -0
- data/sig/commands/schedule_commands.rbs +12 -0
- data/sig/ruby_raider/runtime/retry_reset.rbs +7 -0
- data/sig/ruby_raider/runtime/show_browser.rbs +27 -0
- data/sig/ruby_raider.rbs +4 -0
- data/sig/utilities/schedules/adapter.rbs +5 -0
- data/sig/utilities/schedules/base.rbs +15 -0
- data/sig/utilities/schedules/kind.rbs +32 -0
- data/sig/utilities/schedules/legacy.rbs +45 -0
- data/sig/utilities/schedules/legacy_stub.rbs +15 -0
- data/sig/utilities/{flows → schedules}/next_run.rbs +3 -3
- data/sig/utilities/{flows → schedules}/notifier.rbs +1 -1
- data/sig/utilities/{flows → schedules}/os/command_runner.rbs +1 -1
- data/sig/utilities/{flows → schedules}/os/cron.rbs +7 -7
- data/sig/utilities/{flows → schedules}/os/launchd.rbs +7 -6
- data/sig/utilities/{flows → schedules}/os/linux.rbs +10 -4
- data/sig/utilities/{flows → schedules}/os/systemd.rbs +8 -7
- data/sig/utilities/{flows → schedules}/os/task_scheduler.rbs +13 -10
- data/sig/utilities/schedules/os_enumerator.rbs +12 -0
- data/sig/utilities/schedules/project.rbs +22 -0
- data/sig/utilities/{flows/schedule.rbs → schedules/rule.rbs} +23 -12
- data/sig/utilities/schedules/run_lock.rbs +29 -0
- data/sig/utilities/schedules/run_log.rbs +33 -0
- data/sig/utilities/schedules/run_report.rbs +34 -0
- data/sig/utilities/schedules/run_wrapper.rbs +45 -0
- data/sig/utilities/schedules/schedule.rbs +27 -0
- data/sig/utilities/schedules/schedule_store.rbs +21 -0
- data/sig/utilities/{flows → schedules}/scheduler.rbs +22 -15
- data/sig/utilities/schedules/scheduler_steps.rbs +22 -0
- data/sig/utilities/schedules/selection.rbs +36 -0
- data/sig/utilities/schedules/spawn.rbs +10 -0
- data/sig/utilities/{flows/schedule_store.rbs → schedules/store.rbs} +14 -9
- data/spec/commands/schedule_commands_spec.rb +284 -0
- data/spec/integration/content/common_content_spec.rb +8 -2
- data/spec/integration/content/gemfile_content_spec.rb +2 -2
- data/spec/integration/end_to_end_spec.rb +0 -63
- data/spec/runtime/retry_reset_spec.rb +112 -0
- data/spec/runtime/show_browser_project_spec.rb +70 -0
- data/spec/runtime/show_browser_spec.rb +101 -0
- data/spec/utilities/schedules/kind_spec.rb +81 -0
- data/spec/utilities/schedules/legacy_spec.rb +433 -0
- data/spec/utilities/schedules/legacy_stub_spec.rb +144 -0
- data/spec/utilities/{flows → schedules}/next_run_spec.rb +5 -5
- data/spec/utilities/{flows → schedules}/notifier_spec.rb +3 -4
- data/spec/utilities/{flows → schedules}/os/command_runner_spec.rb +2 -3
- data/spec/utilities/{flows → schedules}/os/cron_spec.rb +34 -29
- data/spec/utilities/{flows → schedules}/os/launchd_spec.rb +42 -29
- data/spec/utilities/{flows → schedules}/os/linux_spec.rb +27 -13
- data/spec/utilities/schedules/os/list_ids_spec.rb +202 -0
- data/spec/utilities/{flows → schedules}/os/systemd_spec.rb +35 -31
- data/spec/utilities/{flows → schedules}/os/task_scheduler_spec.rb +40 -34
- data/spec/utilities/schedules/os_entry_spec.rb +54 -0
- data/spec/utilities/schedules/os_enumerator_spec.rb +57 -0
- data/spec/utilities/schedules/project_spec.rb +125 -0
- data/spec/utilities/schedules/run_lock_spec.rb +143 -0
- data/spec/utilities/schedules/run_log_spec.rb +83 -0
- data/spec/utilities/schedules/run_report_spec.rb +150 -0
- data/spec/utilities/schedules/run_wrapper_integration_spec.rb +166 -0
- data/spec/utilities/schedules/run_wrapper_spec.rb +430 -0
- data/spec/utilities/schedules/schedule_spec.rb +120 -0
- data/spec/utilities/schedules/schedule_store_spec.rb +151 -0
- data/spec/utilities/schedules/scheduler_launchd_spec.rb +77 -0
- data/spec/utilities/schedules/scheduler_spec.rb +445 -0
- data/spec/utilities/schedules/selection_spec.rb +176 -0
- data/spec/utilities/schedules/spawn_spec.rb +104 -0
- metadata +106 -53
- data/lib/commands/flow_commands.rb +0 -104
- data/lib/generators/templates/flows/flow_helper.tt +0 -42
- data/lib/generators/templates/flows/run.tt +0 -332
- data/lib/utilities/flows/project.rb +0 -170
- data/lib/utilities/flows/run_log.rb +0 -61
- data/lib/utilities/flows/runner.rb +0 -206
- data/lib/utilities/flows/schedule.rb +0 -221
- data/lib/utilities/flows/scheduler.rb +0 -187
- data/sig/commands/flow_commands.rbs +0 -13
- data/sig/utilities/flows/project.rbs +0 -46
- data/sig/utilities/flows/run_log.rbs +0 -22
- data/sig/utilities/flows/runner.rbs +0 -35
- data/spec/commands/flow_commands_spec.rb +0 -128
- data/spec/generators/flow_helper_spec.rb +0 -40
- data/spec/integration/flows/flow_project.rb +0 -62
- data/spec/integration/flows/run_command_spec.rb +0 -91
- data/spec/integration/flows/run_lifecycle_spec.rb +0 -383
- data/spec/utilities/flows/project_spec.rb +0 -178
- data/spec/utilities/flows/run_log_spec.rb +0 -47
- data/spec/utilities/flows/runner_spec.rb +0 -313
- data/spec/utilities/flows/schedule_spec.rb +0 -233
- data/spec/utilities/flows/schedule_store_spec.rb +0 -137
- data/spec/utilities/flows/scheduler_spec.rb +0 -329
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 07307a2abf36b62e163df5530bb3666faa9ee1837623909e5242dce43086b8b1
|
|
4
|
+
data.tar.gz: 32388fc57a5acb2e497f68c654a750379a3c40841ac849321d2d083bb0019055
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a6ec8338233eeba0dbe5bc400ff16ea0ec2fa3a3003464b6a9f7c230e7f7ee004f0db0ebd5f8c6ee8759f2192ddd83d5d5298025d40f829fc09e20c74a75647d
|
|
7
|
+
data.tar.gz: 9ce051d82e86b5f28e9d1fb9816f4069d11418ef1b12db53c1c7884a271d08225c45ac7d4f6a1b34545a64761e113140a7edbe7c98eba5c973f5e63b33d176de
|
|
@@ -37,5 +37,8 @@ jobs:
|
|
|
37
37
|
- name: Run command routing tests
|
|
38
38
|
run: bundle exec rspec spec/commands/ --format documentation
|
|
39
39
|
|
|
40
|
+
- name: Run schedule tests
|
|
41
|
+
run: bundle exec rspec spec/utilities/schedules --tag ~slow --format documentation
|
|
42
|
+
|
|
40
43
|
- name: Run menu tests
|
|
41
44
|
run: bundle exec rspec spec/menus/ --format documentation
|
data/README.md
CHANGED
|
@@ -198,62 +198,61 @@ Example projects log in once and restore the saved session for examples tagged `
|
|
|
198
198
|
- The state stays in git-ignored `.raider/auth/`.
|
|
199
199
|
- Skeletons (`examples:false`) get `SessionState#reuse` to wire your own login.
|
|
200
200
|
|
|
201
|
-
###
|
|
201
|
+
### Scheduling tests
|
|
202
202
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
```
|
|
206
|
-
raider flow init # adds flows/flow_helper.rb, flows/support/run.rb and .gitignore lines
|
|
207
|
-
raider flow run timesheet # runs flows/timesheet_flow.rb headless
|
|
208
|
-
raider flow run timesheet --headed --retries 2 --input hours=7
|
|
209
|
-
raider flow list [--json] # each flow and its last result
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
```ruby
|
|
213
|
-
# flows/timesheet_flow.rb
|
|
214
|
-
require_relative 'flow_helper'
|
|
215
|
-
|
|
216
|
-
RSpec.describe 'Fill the timesheet' do
|
|
217
|
-
it 'saves the hours' do
|
|
218
|
-
driver.navigate.to 'https://example.com/timesheet'
|
|
219
|
-
driver.find_element(label: 'Hours').send_keys(flow_input('hours', '8'))
|
|
220
|
-
driver.find_element(by_role('button', name: 'Save')).click
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
- **Exit codes:** 0 completed, 1 a step failed, 2 setup failed (bundle, driver, missing helper), 3 the flow is already running. Each flow holds its own lock in `.raider/flows/`, and a second run exits 3 straight away.
|
|
226
|
-
- **Results:** each run gets its own `flow-results/<name>/<UTC time>/` (report, failure screenshot, video and trace), and `.raider/flows/<name>/runs.jsonl` gains one line: `started_at`, `finished_at`, `status` (`completed`, `failed`, `setup_error`, `busy`), `failed_step`, `attempt`, `trigger`, `results_dir`. The last 20 result folders and 50 lines are kept. Flow runs never touch `raider-results/`, and test runs never touch `flow-results/`.
|
|
227
|
-
- **Inputs:** `flow_input('name', 'default')` reads `--input name=value` (`RAIDER_INPUT_NAME`). Inputs show up in your shell history and process list, so keep secrets out of them.
|
|
228
|
-
- **Secrets:** put them in `.raider/flows/env` as `KEY=value` lines (`#` comments, no interpolation). The file is loaded only when you own it and it has permissions 600 (`chmod 600 .raider/flows/env`); otherwise the run warns and goes on without it. Values are never written to the run log (they are replaced with `[secret]`), but your flow's own output and errors are printed as they are. On Windows there are no permission bits, so the folder's access rules protect the file: keep the project in your user folder.
|
|
229
|
-
- **The same record every way:** `bundle exec rspec flows/timesheet_flow.rb` writes the same run log as `raider flow run`; `raider flow run` adds retries (whole flow, 30 s apart) and the exit codes. Set `RAIDER_FLOW_HEADED=1` to watch a plain rspec run (`headless: true` in `config/config.yml` still wins).
|
|
230
|
-
- Projects made with 4.0 can add flows: `raider flow init` raises the Gemfile's `ruby_raider` pin to `~> 4.1`; run `bundle install` after it.
|
|
231
|
-
|
|
232
|
-
### Scheduling flows
|
|
233
|
-
|
|
234
|
-
Your computer's own scheduler runs the flow, so nothing needs to stay open. Scheduling never asks for sudo or admin rights.
|
|
203
|
+
Your computer's own scheduler runs the project's tests: all of them, one tag, or a list of spec files. Nothing needs to stay open, and scheduling never asks for sudo or admin rights. `raider sc` is short for `raider schedule`.
|
|
235
204
|
|
|
236
205
|
```bash
|
|
237
|
-
raider
|
|
238
|
-
raider
|
|
239
|
-
raider
|
|
240
|
-
raider
|
|
241
|
-
raider
|
|
206
|
+
raider schedule add nightly --every day --at 02:00 --notify
|
|
207
|
+
raider schedule add smoke --every hour --at :15 --tag smoke --retries 1
|
|
208
|
+
raider schedule add checkout spec/cart_spec.rb spec/checkout_spec.rb --every weekday --at 08:30 --show-browser
|
|
209
|
+
raider schedule add weekly --every week --on fri --at 18:00 --project ~/code/my_tests
|
|
210
|
+
raider schedule list [--json] # each schedule, its next run, last result and OS state
|
|
211
|
+
raider schedule run nightly # run it now, the way the scheduler does
|
|
212
|
+
raider schedule remove nightly
|
|
242
213
|
```
|
|
243
214
|
|
|
244
|
-
- **Rules:** `--every hour|day|weekday|week`. `--at HH:MM` (24h, local time) is required for `day`, `weekday` and `week`; for `hour` use `--at :MM` (default `:00`). `--on mon|tue|wed|thu|fri|sat|sun` is required for `week` and refused otherwise.
|
|
245
|
-
- **
|
|
246
|
-
- **
|
|
247
|
-
-
|
|
248
|
-
- **
|
|
249
|
-
- **
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
- **
|
|
215
|
+
- **Rules:** `--every hour|day|weekday|week`. `--at HH:MM` (24h, local time) is required for `day`, `weekday` and `week`; for `hour` use `--at :MM` (default `:00`). `--on mon|tue|wed|thu|fri|sat|sun` is required for `week` and refused otherwise. Names use 1 to 60 of `a-z`, `0-9` and `_`. Adding a name again replaces its schedule.
|
|
216
|
+
- **What runs:** no files and no `--tag` runs all tests. `--tag TAG` runs one tag (letters, digits, `_`, `:` and `-`). Spec files are relative paths inside the project ending in `_spec.rb`, at most 200, and they can go before or after the options. `--tag` and files can't be used together.
|
|
217
|
+
- **Your project's gem:** the scheduler runs `bundle exec raider schedule run` in your project, so `add` checks its `Gemfile.lock` first and refuses an older ruby_raider ("Scheduling needs ruby_raider X or later in this project: bundle update ruby_raider"), or a project with no `Gemfile.lock` yet (run `bundle install` first).
|
|
218
|
+
- **`--project PATH`:** works from any folder. Put it after the subcommand (`raider schedule add nightly ... --project DIR`); before the subcommand Thor shows the help instead.
|
|
219
|
+
- **Per machine:** schedules live in `.raider/schedules/schedules.yml`, which `add` git-ignores (it holds machine paths; projects made with 4.2 have the line already), so a fresh clone has none. The OS entry holds only the schedule's name: `raider schedule run NAME --project DIR --trigger schedule`. The selection, retries, notify and show-the-browser setting are read from schedules.yml at each run, which checks every entry again before anything starts.
|
|
220
|
+
- **Show the browser:** scheduled runs are hidden (`HEADLESS=true`) unless you add `--show-browser`. It shows the browser whatever `headless` says in `config/config.yml`, and no driver change is needed: the runtime handles it (`RAIDER_SHOW_BROWSER=1` drops `--headless`). Neither config.yml nor `helpers/driver_helper.rb` is written.
|
|
221
|
+
|
|
222
|
+
| Project Headless (config.yml) | Show the browser off | Show the browser on |
|
|
223
|
+
|---|---|---|
|
|
224
|
+
| off / absent | `HEADLESS=true`: hidden | `RAIDER_SHOW_BROWSER=1`, `HEADLESS` removed: visible |
|
|
225
|
+
| on | `HEADLESS=true`: hidden | `RAIDER_SHOW_BROWSER=1`: visible (the runtime drops `--headless`) |
|
|
226
|
+
|
|
227
|
+
- **Where a browser can show:** on macOS while you are logged in (a launchd agent runs in your GUI session). On Windows the task runs in your session while you are logged on; a visible browser there is not verified yet. On Linux `--show-browser` is refused ("Show the browser isn't available for scheduled runs on Linux"): systemd user units and cron have no display. A project made before 4.0 is refused too ("Show the browser needs a project made with Ruby Raider 4.0 or later").
|
|
228
|
+
- **Retries:** `--retries N` (0 to 3) re-runs each failed test up to N times inside the same run, with rspec-retry (`RETRY_COUNT` is N + 1). From 4.2 the runtime gives a retried test a fresh browser. Before 4.2, a retry in a generated project failed with a refused connection, because the driver was already quit. The fix is in the gem (`ruby_raider/runtime/rspec`), so it also applies to manual runs such as `RETRY_COUNT=2 bundle exec rspec`; `bundle update ruby_raider` brings it to existing projects. A stop doesn't cut rspec-retry's tries short: the test running when you stop may still get its remaining tries before the run ends.
|
|
229
|
+
- **Results:** a run is a normal `bundle exec rspec`, so its report goes to `raider-results/` and shows on the Dashboard. Each run also adds one line to `.raider/schedules/<name>/runs.jsonl` (`status` `passed`, `failed`, `setup_error` or `busy`, the counts and the trigger; the last 50 lines are kept), and the last report is copied to `.raider/schedules/<name>/last/` (run.json and index.html, plus the attachments when they come to 50 MB or less).
|
|
230
|
+
- **Exit codes:** `raider schedule run` exits 0 passed, 1 failed, 2 could not start or refused, 3 another run was going (another `raider schedule run` in the project, or any run that holds `.raider/schedules/running.json`). Only one scheduled run per project goes at a time, and a busy run leaves `raider-results/` alone. Other commands exit 0, or 2 with one line on stderr. Thor's own usage errors (a missing NAME, a bad `--retries`) exit 1 with Thor's text.
|
|
231
|
+
- **Notify:** `--notify` shows a desktop notification only when a run failed or could not start (not when it passed, was busy or was stopped). The title is "Ruby Raider: <name> failed"; the body says how many tests failed, or why the run could not start. macOS uses `osascript`, Linux `notify-send`, Windows a PowerShell toast. A notification that can't be shown prints a warning and never changes the exit code. On Linux `notify-send` needs your desktop session bus, so under cron, and in a systemd user session that has not imported it, it usually can't show.
|
|
232
|
+
- **Schedulers:** macOS uses launchd (`~/Library/LaunchAgents/dev.rubyraider.schedule.<hash>.<name>.plist`). Windows uses Task Scheduler (a task in the `\RubyRaider\` folder). Linux uses a systemd user timer in `~/.config/systemd/user`, or, when `systemctl --user` does not answer, one tagged line in your crontab. Times are local time (cron uses the system time zone).
|
|
233
|
+
- **Missed runs:** launchd runs a run missed while the Mac slept once on wake. Task Scheduler runs it as soon as it can. systemd (`Persistent=true`) runs it once your user manager is back, at the next boot or login. cron skips it, and `raider schedule list` says so.
|
|
234
|
+
- **Logged in:** on macOS and Windows a run happens only while you are logged in. On Linux, systemd user timers stop when you log out unless lingering is on: `loginctl enable-linger $USER` (raider does not run it). A systemd schedule can only be removed from a session where `systemctl --user` works; until then `remove` refuses and keeps the entry.
|
|
235
|
+
- **States:** `ok`, `missing from launchd` (or `Task Scheduler`, `systemd`, `cron`) when the OS entry is gone, `missing from schedules.yml` when the OS still runs a schedule the file does not list, `Ruby not found: re-schedule`, `test file not found: <file>` when a listed spec file was deleted or renamed, and `unknown (...)` when the scheduler could not be asked.
|
|
236
|
+
- **Ruby:** the OS entry runs the absolute `ruby` and `bundle` paths captured when you added it, plus `GEM_HOME` and `GEM_PATH` on macOS and Linux. After a Ruby upgrade or move, run `raider schedule add` again.
|
|
237
|
+
- **Logs:** on macOS and Linux the scheduler's output goes to `.raider/schedules/<name>/scheduler.log`, moved to `scheduler.log.1` at the start of a scheduled run once it is over 1 MB. Windows tasks keep no scheduler.log; each run's result is in `runs.jsonl` as always.
|
|
238
|
+
- **Windows refusals:** Task Scheduler and ruby.exe would expand `%...%`, `*`, `?`, `[` or `{`, so a spec file or path holding them is refused: rename the file or use a tag.
|
|
239
|
+
- **Moving the project:** each OS entry is tied to the project's path (`<hash>` is made from it). Run `raider schedule remove` before you move the project. Entries made before a move keep the old path's id, so `list` in the new place can't find them. Remove them by hand; their names start with `dev.rubyraider.schedule.`:
|
|
240
|
+
- macOS: `launchctl bootout gui/$UID/<label>`, then delete `~/Library/LaunchAgents/<label>.plist`.
|
|
241
|
+
- Windows: `schtasks /Delete /TN \RubyRaider\<id> /F`.
|
|
242
|
+
- Linux (systemd): `systemctl --user disable --now <id>.timer`, delete `<id>.timer` and `<id>.service` from `~/.config/systemd/user`, then `systemctl --user daemon-reload`.
|
|
243
|
+
- Linux (cron): `crontab -e` and delete the `# raider-schedule <id>` line and the line under it.
|
|
244
|
+
- **Tested on:** macOS (launchd) is tested against the real scheduler: adding, a run started by launchd hidden and shown with Headless on, a failed run with a notification, a busy run, and removing. Task Scheduler, systemd and cron are covered by unit tests with a fake scheduler only.
|
|
245
|
+
|
|
246
|
+
### Upgrading from 4.1
|
|
247
|
+
|
|
248
|
+
- **Old schedules are removed automatically.** Schedules made with Ruby Raider 4.1 can't run in 4.2. They are removed the first time any `raider schedule` command runs in the project (such as `raider schedule list`), or the next time one of them fires. One line says which were removed.
|
|
249
|
+
- **Add them again** with `raider schedule add`.
|
|
250
|
+
- **Leftover folders:** `flows/`, `flow-results/` and `.raider/flows/` (it holds old secrets in `env`) are left alone. Delete them only after the line saying the old schedules were removed has appeared (run any `raider schedule` command, such as `raider schedule list`): the cleanup looks for `.raider/flows/`, and the old entries write their logs there.
|
|
251
|
+
- **A project moved after scheduling** can't be cleaned automatically: its entries are tied to the old path. Remove them by hand; their names start with `dev.rubyraider.flow.`:
|
|
252
|
+
- macOS: find them with `ls ~/Library/LaunchAgents | grep dev.rubyraider.flow.`, run `launchctl bootout gui/$(id -u)/<label>`, then delete `~/Library/LaunchAgents/<label>.plist` (`<label>` is `dev.rubyraider.flow.<hash>.<name>`).
|
|
253
|
+
- Windows: find them with `schtasks /Query /FO CSV | findstr dev.rubyraider.flow.`, then `schtasks /Delete /TN "\RubyRaider\<id>" /F`.
|
|
254
|
+
- Linux (systemd): find them with `ls ~/.config/systemd/user | grep dev.rubyraider.flow.` (or `$XDG_CONFIG_HOME/systemd/user` when it is set), run `systemctl --user disable --now <id>.timer`, delete `<id>.timer` and `<id>.service` from `~/.config/systemd/user`, then `systemctl --user daemon-reload`.
|
|
255
|
+
- Linux (cron): find them with `crontab -l | grep -A1 '# raider-flow '`, then `crontab -e` and delete each `# raider-flow <id>` line and the line under it.
|
|
257
256
|
|
|
258
257
|
### Also included
|
|
259
258
|
|
|
@@ -327,12 +326,12 @@ The skeleton keeps the helpers, config, CI and README, and its empty suite passe
|
|
|
327
326
|
raider new [PROJECT_NAME] # Create a new framework project
|
|
328
327
|
raider generate # Access scaffolding commands
|
|
329
328
|
raider utility # Access utility commands
|
|
330
|
-
raider
|
|
329
|
+
raider schedule # Schedule the tests: add, list, remove, run
|
|
331
330
|
raider version # Show current version
|
|
332
331
|
raider help [COMMAND] # Describe available commands
|
|
333
332
|
```
|
|
334
333
|
|
|
335
|
-
Shortcuts: `n` (new), `g` (generate), `u` (utility), `
|
|
334
|
+
Shortcuts: `n` (new), `g` (generate), `u` (utility), `sc` (schedule), `v` (version)
|
|
336
335
|
|
|
337
336
|
### Scaffolding Commands
|
|
338
337
|
|
data/RELEASE.md
CHANGED
|
@@ -2,7 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## Release Notes
|
|
4
4
|
|
|
5
|
-
### 4.
|
|
5
|
+
### 4.2.0
|
|
6
|
+
|
|
7
|
+
- `raider schedule add NAME [SPEC_FILE ...] --every hour|day|weekday|week [--at HH:MM | :MM] [--on mon..sun] [--tag TAG] [--show-browser] [--retries 0..3] [--notify]` runs the project's tests with the computer's own scheduler (launchd, Task Scheduler, a systemd user timer or cron, as in 4.1). `raider schedule list [--json]`, `remove NAME` and `run NAME [--trigger manual|schedule]` complete it; every command takes `--project PATH`. `raider sc` is short for `raider schedule`.
|
|
8
|
+
- A schedule runs all tests, one tag (`--tag`), or up to 200 spec files given by path (relative, inside the project, ending `_spec.rb`); a tag and files can't be combined. Schedules live in `.raider/schedules/schedules.yml`, which `add` git-ignores; projects made with `raider new` 4.2 have the `.raider/schedules/` line from the start. The OS entry holds only the schedule's name: the selection and options are read from the yml, and checked again, at each run.
|
|
9
|
+
- `--show-browser` shows the browser in a scheduled run whatever the project's `headless` setting says, with no project file changed: when `RAIDER_SHOW_BROWSER=1`, the runtime drops `--headless` from the Chrome, Edge and Firefox options. Refused for projects made before 4.0 and on Linux; on macOS the user must be logged in, and Windows is unverified.
|
|
10
|
+
- `--retries N` re-runs each failed test up to N times inside the same run with rspec-retry (`RETRY_COUNT = N + 1`). A stop doesn't cut the tries of the test in flight short.
|
|
11
|
+
- `raider schedule add` refuses a project whose `Gemfile.lock` resolves an older ruby_raider, or that has no `Gemfile.lock` yet, so a schedule never fires into a bundle without `raider schedule`.
|
|
12
|
+
- Retried tests get a fresh browser. Before this, a retry in a generated 4.x project failed with a refused connection because the driver was already quit. The fix is in the runtime (`ruby_raider/runtime/rspec`), so it reaches every 4.x project on `bundle update ruby_raider` and also applies to manual `RETRY_COUNT=2 bundle exec rspec` runs.
|
|
13
|
+
- Scheduled results go to `raider-results/` (the Dashboard), one line per run in `.raider/schedules/<name>/runs.jsonl` (last 50 kept), and a copy of the last report in `.raider/schedules/<name>/last/`. `scheduler.log` is rotated once over 1 MB.
|
|
14
|
+
- `--notify` shows a desktop notification ("Ruby Raider: <name> failed") only when a run failed or could not start, with 4.1's channels.
|
|
15
|
+
- `raider schedule run` exits 0 passed, 1 failed, 2 could not start or refused, 3 another run was going: another `raider schedule run` in the project, or any run that holds `.raider/schedules/running.json` (a busy run leaves `raider-results/` alone).
|
|
16
|
+
- `raider schedule` runs your tests on a schedule. The 4.1 recording-without-checks commands are gone, and their schedules are removed automatically: the first `raider schedule` command in a project, or the old entry's next firing, removes them and prints one line. The folders they used are left for you to delete (see Upgrading from 4.1 in the README).
|
|
17
|
+
|
|
18
|
+
### 4.1.0 (2026-11-10)
|
|
6
19
|
|
|
7
20
|
- Flows: recordings without assertions that run on their own. `raider flow init` adds `flows/flow_helper.rb` and `flows/support/run.rb` to a Selenium web project made with 4.0 or later (and raises a `~> 4.0` Gemfile pin to `~> 4.1`); mobile and 3.x projects are refused with exit 2.
|
|
8
21
|
- `raider flow run NAME` runs `flows/NAME_flow.rb` headless with `bundle exec rspec`, exiting 0 completed, 1 a step failed, 2 setup failed, 3 already running. `--headed`, `--retries N` (0 to 3, whole flow, 30 s apart), `--input name=value` (read with `flow_input`) and `--trigger manual|schedule|desktop`. `--project PATH` works from any folder.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
require_relative '../utilities/schedules/legacy'
|
|
5
|
+
require_relative '../utilities/schedules/scheduler'
|
|
6
|
+
require_relative '../utilities/schedules/run_wrapper'
|
|
7
|
+
|
|
8
|
+
# raider schedule: runs the project's tests (all, one tag or listed spec files) with the OS scheduler.
|
|
9
|
+
# Spec files are positional, never an array option, so they mix with the options in any order.
|
|
10
|
+
# Every command first removes the project's 4.1 schedules (Legacy), which never changes its exit code or stdout.
|
|
11
|
+
class ScheduleCommands < Thor
|
|
12
|
+
def self.exit_on_failure? = true
|
|
13
|
+
|
|
14
|
+
class_option :project, type: :string, desc: 'The project folder (default: the current folder)'
|
|
15
|
+
|
|
16
|
+
desc 'add NAME [SPEC_FILE ...]', 'Schedules the tests (all, --tag or the spec files), or replaces the schedule NAME'
|
|
17
|
+
option :every, type: :string, desc: 'hour, day, weekday or week'
|
|
18
|
+
option :at, type: :string, desc: 'HH:MM (24h, local time); for every hour :MM'
|
|
19
|
+
option :on, type: :string, desc: 'mon to sun, for every week'
|
|
20
|
+
option :tag, type: :string, desc: 'Run only the tests with this tag'
|
|
21
|
+
option :show_browser, type: :boolean, desc: 'Show the browser, whatever the Headless setting'
|
|
22
|
+
option :retries, type: :numeric, default: 0, desc: 'Re-run each failed test up to N times (0-3)'
|
|
23
|
+
option :notify, type: :boolean, desc: 'Show a desktop notification when a scheduled run fails'
|
|
24
|
+
|
|
25
|
+
def add(name, *files)
|
|
26
|
+
scheduler.add(name, every: options[:every], files: files, tag: options[:tag], at: options[:at], on: options[:on],
|
|
27
|
+
retries: options[:retries], notify: options[:notify] || false,
|
|
28
|
+
show_browser: options[:show_browser] || false)
|
|
29
|
+
rescue RubyRaider::Schedules::Error => e
|
|
30
|
+
stop(e)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
desc 'list', 'Lists the schedules with their next run and last result'
|
|
34
|
+
option :json, type: :boolean, desc: 'Print JSON for the desktop app'
|
|
35
|
+
|
|
36
|
+
def list
|
|
37
|
+
project = new_project
|
|
38
|
+
notice = cleanup(project)
|
|
39
|
+
RubyRaider::Schedules::Scheduler.new(project).list(json: options[:json] || false, notice: notice)
|
|
40
|
+
rescue RubyRaider::Schedules::Error => e
|
|
41
|
+
stop(e)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
desc 'remove NAME', 'Removes the schedule NAME from the OS scheduler and schedules.yml'
|
|
45
|
+
|
|
46
|
+
def remove(name)
|
|
47
|
+
scheduler.remove(name)
|
|
48
|
+
rescue RubyRaider::Schedules::Error => e
|
|
49
|
+
stop(e)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# `run` is a Thor reserved word, so the method has another name
|
|
53
|
+
desc 'run NAME', 'Runs the schedule NAME now: exit 0 passed, 1 failed, 2 setup error, 3 busy'
|
|
54
|
+
option :trigger, type: :string, default: 'manual', desc: 'manual or schedule (what the OS entry passes)'
|
|
55
|
+
|
|
56
|
+
def run_schedule(name)
|
|
57
|
+
exit(RubyRaider::Schedules::RunWrapper.new(cleaned_project).run(name, trigger: options[:trigger]))
|
|
58
|
+
rescue RubyRaider::Schedules::Error => e
|
|
59
|
+
stop(e)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
map 'run' => :run_schedule
|
|
63
|
+
|
|
64
|
+
no_commands do
|
|
65
|
+
def new_project = RubyRaider::Schedules::Project.new(options[:project] || Dir.pwd)
|
|
66
|
+
|
|
67
|
+
# The project, once its 4.1 schedules are cleaned up
|
|
68
|
+
def cleaned_project = new_project.tap { |project| cleanup(project) }
|
|
69
|
+
|
|
70
|
+
# Removes the project's 4.1 schedules; the notice (on stderr already), nil when there was nothing to remove.
|
|
71
|
+
# Without the 4.1 folder (Legacy.folder?) there is none, so that costs one lstat.
|
|
72
|
+
def cleanup(project)
|
|
73
|
+
RubyRaider::Schedules::Legacy.new(project).cleanup if RubyRaider::Schedules::Legacy.folder?(project)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def scheduler = RubyRaider::Schedules::Scheduler.new(cleaned_project)
|
|
77
|
+
|
|
78
|
+
# One stderr line, whatever the message holds (a --project path can carry a newline)
|
|
79
|
+
def stop(error)
|
|
80
|
+
warn(RubyRaider::Schedules.printable(error.message))
|
|
81
|
+
exit(error.exit_code)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Raider
|
|
4
|
+
# rspec-retry runs a retried example again in the same example group instance. The project's after
|
|
5
|
+
# hook has quit the driver by then, and its driver_helper memoises it in @driver, so the retry would
|
|
6
|
+
# reuse a dead session. Before each retry this clears @driver: the next `driver` call starts a fresh
|
|
7
|
+
# one. Nothing is quit here.
|
|
8
|
+
#
|
|
9
|
+
# Installed from a before(:suite) hook, so it sees the final configuration whatever the require
|
|
10
|
+
# order. Without rspec-retry it does nothing. A retry_callback the project set is kept as it is,
|
|
11
|
+
# and then the project owns the reset.
|
|
12
|
+
#
|
|
13
|
+
# It relies on the after hook quitting the driver. A project whose after hook does not quit it leaves
|
|
14
|
+
# one browser open per retry (the cleared @driver is never quit); such a project should set its own
|
|
15
|
+
# retry_callback that quits and clears the driver.
|
|
16
|
+
module RetryReset
|
|
17
|
+
CALLBACK = proc { |_example| @driver = nil }
|
|
18
|
+
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
# True when the callback was set
|
|
22
|
+
def install(config = ::RSpec.configuration)
|
|
23
|
+
return false unless config.respond_to?(:retry_callback=)
|
|
24
|
+
return false if config.retry_callback
|
|
25
|
+
|
|
26
|
+
config.retry_callback = CALLBACK
|
|
27
|
+
true
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
::RSpec.configure { |config| config.before(:suite) { Raider::RetryReset.install } } if defined?(::RSpec::Core)
|
|
@@ -7,6 +7,7 @@ require_relative 'matchers/element'
|
|
|
7
7
|
require_relative 'matchers/page'
|
|
8
8
|
require_relative 'matchers/presence'
|
|
9
9
|
require_relative 'matchers/retrying_block'
|
|
10
|
+
require_relative 'retry_reset'
|
|
10
11
|
|
|
11
12
|
module Raider
|
|
12
13
|
# Web-first RSpec matchers that retry until the page settles.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Raider
|
|
4
|
+
# Show the browser whatever the project's headless setting says. A scheduled or desktop run sets
|
|
5
|
+
# RAIDER_SHOW_BROWSER=1; the project's driver_helper still adds --headless, and the filter prepended
|
|
6
|
+
# here drops it. config.yml and driver_helper.rb are never written. Unset, nothing is prepended.
|
|
7
|
+
module ShowBrowser
|
|
8
|
+
ENV_KEY = 'RAIDER_SHOW_BROWSER'
|
|
9
|
+
OPTIONS = %w[Chrome Edge Firefox].map { |browser| "Selenium::WebDriver::#{browser}::Options" }.freeze
|
|
10
|
+
SWITCHES = %w[--headless -headless].freeze
|
|
11
|
+
PREFIX = '--headless='
|
|
12
|
+
ARGS_KEYS = [:args, 'args'].freeze
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def headless?(arg) = arg.is_a?(String) && (SWITCHES.include?(arg) || arg.start_with?(PREFIX))
|
|
17
|
+
|
|
18
|
+
def without_headless(args) = args.is_a?(Array) ? args.reject { |arg| headless?(arg) } : args
|
|
19
|
+
|
|
20
|
+
# The Options classes the filter is on; empty unless the env value is exactly '1'
|
|
21
|
+
def install(env = ENV)
|
|
22
|
+
return [] unless env[ENV_KEY] == '1'
|
|
23
|
+
|
|
24
|
+
OPTIONS.filter_map do |name|
|
|
25
|
+
next unless Object.const_defined?(name)
|
|
26
|
+
|
|
27
|
+
klass = Object.const_get(name)
|
|
28
|
+
klass.prepend(Filter) unless klass.include?(Filter)
|
|
29
|
+
klass
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Drops the headless switches from args: given to new, from add_argument and from args=
|
|
34
|
+
# :reek:ModuleInitialize -- prepended to the Options classes: its initialize wraps theirs
|
|
35
|
+
module Filter
|
|
36
|
+
def initialize(**opts)
|
|
37
|
+
ARGS_KEYS.each { |key| opts[key] = ShowBrowser.without_headless(opts[key]) if opts.key?(key) }
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# The args, as Selenium's own add_argument returns them, also when the switch is dropped
|
|
42
|
+
def add_argument(arg)
|
|
43
|
+
return options[:args] if ShowBrowser.headless?(arg)
|
|
44
|
+
|
|
45
|
+
super
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def args=(value)
|
|
49
|
+
super(ShowBrowser.without_headless(value))
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
data/lib/ruby_raider/runtime.rb
CHANGED
|
@@ -14,7 +14,9 @@ require_relative 'runtime/observer'
|
|
|
14
14
|
require_relative 'runtime/actionability'
|
|
15
15
|
require_relative 'runtime/strict'
|
|
16
16
|
require_relative 'runtime/actionable'
|
|
17
|
+
require_relative 'runtime/show_browser'
|
|
17
18
|
|
|
18
19
|
Raider::Finders.check_api
|
|
19
20
|
Raider::Finders.install
|
|
20
21
|
Raider::Finders.guard
|
|
22
|
+
Raider::ShowBrowser.install
|
data/lib/ruby_raider.rb
CHANGED
|
@@ -5,23 +5,38 @@ require 'thor'
|
|
|
5
5
|
# Lazy-load top-level command classes to reduce CLI startup time.
|
|
6
6
|
autoload :ScaffoldingCommands, File.expand_path('commands/scaffolding_commands', __dir__)
|
|
7
7
|
autoload :UtilityCommands, File.expand_path('commands/utility_commands', __dir__)
|
|
8
|
-
autoload :
|
|
8
|
+
autoload :ScheduleCommands, File.expand_path('commands/schedule_commands', __dir__)
|
|
9
9
|
|
|
10
10
|
module RubyRaider
|
|
11
11
|
class Raider < Thor
|
|
12
12
|
def self.exit_on_failure? = true
|
|
13
13
|
|
|
14
|
-
#
|
|
15
|
-
|
|
14
|
+
# The commands whose arguments are checked for valid UTF-8 before Thor sees them
|
|
15
|
+
GUARDED = %w[schedule sc].freeze
|
|
16
|
+
# What a Ruby Raider 4.1 OS entry runs (`raider flow run ...`): a hidden stub, not a Thor command
|
|
17
|
+
LEGACY = %w[flow f].freeze
|
|
18
|
+
|
|
19
|
+
# The 4.1 stub goes first, whatever its argv holds. Thor runs a regex on every argument before any
|
|
20
|
+
# command, so invalid UTF-8 would crash it with a backtrace; a schedule command says which argument
|
|
21
|
+
# instead and exits 2 like its other refusals
|
|
16
22
|
def self.start(given_args = ARGV, config = {})
|
|
23
|
+
exit(legacy_stub(given_args)) if LEGACY.include?(given_args.first)
|
|
17
24
|
bad = given_args.find { |arg| arg.is_a?(String) && !arg.valid_encoding? }
|
|
18
|
-
if bad &&
|
|
25
|
+
if bad && GUARDED.include?(given_args.first)
|
|
19
26
|
warn("Invalid argument #{bad.inspect}: use valid UTF-8")
|
|
20
27
|
exit(2)
|
|
21
28
|
end
|
|
22
29
|
super
|
|
23
30
|
end
|
|
24
31
|
|
|
32
|
+
# Removes the project's 4.1 schedules and says why the command is gone; always 2
|
|
33
|
+
def self.legacy_stub(given_args)
|
|
34
|
+
require_relative 'utilities/schedules/legacy_stub'
|
|
35
|
+
Schedules::LegacyStub.run(given_args.drop(1))
|
|
36
|
+
rescue StandardError, ScriptError
|
|
37
|
+
2
|
|
38
|
+
end
|
|
39
|
+
|
|
25
40
|
no_tasks do
|
|
26
41
|
def current_version = File.read(File.expand_path('version', __dir__)).strip
|
|
27
42
|
end
|
|
@@ -63,9 +78,9 @@ module RubyRaider
|
|
|
63
78
|
subcommand 'utility', UtilityCommands
|
|
64
79
|
map 'u' => 'utility'
|
|
65
80
|
|
|
66
|
-
desc '
|
|
67
|
-
subcommand '
|
|
68
|
-
map '
|
|
81
|
+
desc 'schedule', 'Runs the tests on a schedule with the OS scheduler (add, list, remove, run)'
|
|
82
|
+
subcommand 'schedule', ScheduleCommands
|
|
83
|
+
map 'sc' => 'schedule'
|
|
69
84
|
|
|
70
85
|
no_tasks do
|
|
71
86
|
# -p values arrive as strings; examples must be true or false, in any case
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rbconfig'
|
|
4
|
+
require_relative 'base'
|
|
5
|
+
require_relative 'os/command_runner'
|
|
6
|
+
require_relative 'os/launchd'
|
|
7
|
+
require_relative 'os/task_scheduler'
|
|
8
|
+
require_relative 'os/linux'
|
|
9
|
+
|
|
10
|
+
module RubyRaider
|
|
11
|
+
module Schedules
|
|
12
|
+
# The OS scheduler for this machine, for schedules of the kind; only Linux asks the system (is the user's systemd there?)
|
|
13
|
+
def self.adapter(kind:, runner: Os::CommandRunner.new, host_os: RbConfig::CONFIG['host_os'])
|
|
14
|
+
case host_os
|
|
15
|
+
when /darwin/ then Os::Launchd.new(kind: kind, runner: runner)
|
|
16
|
+
when /mswin|mingw|cygwin/ then Os::TaskScheduler.new(kind: kind, runner: runner)
|
|
17
|
+
when /linux/ then Os::Linux.pick(runner, kind: kind)
|
|
18
|
+
else raise Error, kind.unsupported(host_os)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyRaider
|
|
4
|
+
# The OS scheduler (launchd, Task Scheduler, systemd, cron) shared by test schedules and the 4.1 ones
|
|
5
|
+
# the cleanup removes. What differs between them is a Kind.
|
|
6
|
+
module Schedules
|
|
7
|
+
NAME = /\A[a-z0-9_]{1,60}\z/
|
|
8
|
+
# Schedule::DAYS order, as every adapter counts it
|
|
9
|
+
DAYS = %w[mon tue wed thu fri sat sun].freeze
|
|
10
|
+
|
|
11
|
+
def self.valid_name?(name) = NAME.match?(name.to_s)
|
|
12
|
+
|
|
13
|
+
# Text from a log or a command, safe to print: it can't move the cursor or forge lines, and bytes that
|
|
14
|
+
# are not valid in its encoding become ? too (a regexp would raise on them)
|
|
15
|
+
def self.printable(text) = text.to_s.scrub('?').gsub(/[[:cntrl:]]/, '?')
|
|
16
|
+
|
|
17
|
+
# A problem that stops a schedule command; exit_code follows `raider schedule run` (2 = setup failed).
|
|
18
|
+
class Error < StandardError
|
|
19
|
+
attr_reader :exit_code
|
|
20
|
+
|
|
21
|
+
def initialize(message, exit_code: 2)
|
|
22
|
+
super(message)
|
|
23
|
+
@exit_code = exit_code
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module RubyRaider
|
|
6
|
+
module Schedules
|
|
7
|
+
# What kind of schedule an OS entry belongs to: the words that name it in each scheduler (os_id prefix,
|
|
8
|
+
# cron tag, descriptions), the command it runs, where its files live and which names it takes.
|
|
9
|
+
# Adapters get one, so an entry of one kind is never read or removed as another.
|
|
10
|
+
# :reek:TooManyInstanceVariables { max_instance_variables: 9 } -- the words, folder and reserved names of one kind
|
|
11
|
+
class Kind
|
|
12
|
+
attr_reader :command, :os_id_prefix, :os_id_pattern, :dir, :noun, :plural, :remove_command, :stranded_hint, :reserved
|
|
13
|
+
|
|
14
|
+
# command is the raider subcommand (`raider <command> run NAME`); it also names the os_id prefix and tags.
|
|
15
|
+
# stranded_hint: what to run for a stranded timer, when it is not `<remove_command> NAME`
|
|
16
|
+
def initialize(command:, dir:, noun:, plural:, remove_command:, stranded_hint: nil, reserved: [])
|
|
17
|
+
@command = command.dup.freeze
|
|
18
|
+
@os_id_prefix = "dev.rubyraider.#{command}".freeze
|
|
19
|
+
@os_id_pattern = /\A#{Regexp.escape(@os_id_prefix)}\.[0-9a-f]{10}\.([a-z0-9_]{1,60})\z/
|
|
20
|
+
@dir = dir.dup.freeze
|
|
21
|
+
@noun = noun.dup.freeze
|
|
22
|
+
@plural = plural.dup.freeze
|
|
23
|
+
@remove_command = remove_command.dup.freeze
|
|
24
|
+
@stranded_hint = stranded_hint&.dup&.freeze
|
|
25
|
+
@reserved = reserved.map { |word| word.dup.freeze }.freeze
|
|
26
|
+
freeze
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def valid_name?(name) = Schedules.valid_name?(name) && !reserved.include?(name.to_s)
|
|
30
|
+
|
|
31
|
+
# The name in an os_id of this kind, nil when it is not one
|
|
32
|
+
def name_in(os_id)
|
|
33
|
+
# Text from the OS (file names, schtasks, crontab) may not be valid UTF-8; a regexp would raise on it
|
|
34
|
+
return nil unless os_id.is_a?(String) && os_id.valid_encoding?
|
|
35
|
+
|
|
36
|
+
name = os_id_pattern.match(os_id)&.[](1)
|
|
37
|
+
name if name && valid_name?(name)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# An os_id of this kind that starts with prefix (one project's: "<os_id_prefix>.<hash10>.")
|
|
41
|
+
def listed?(os_id, prefix) = !name_in(os_id).nil? && os_id.to_s.start_with?(prefix)
|
|
42
|
+
|
|
43
|
+
def cron_tag = "# raider-#{command} "
|
|
44
|
+
|
|
45
|
+
# The command line under a cron tag runs `raider <command> run <name> --project ... --trigger schedule`
|
|
46
|
+
def our_command?(text, name) = text.include?(" exec raider #{command} run #{name} --project ") && text.include?(' --trigger schedule')
|
|
47
|
+
|
|
48
|
+
def task_description(name, rule) = "Ruby Raider #{command} #{name} (#{rule})"
|
|
49
|
+
|
|
50
|
+
def task_file_prefix = "raider-#{command}-task"
|
|
51
|
+
|
|
52
|
+
def service_description(name) = "raider #{command} #{name}"
|
|
53
|
+
|
|
54
|
+
def timer_description(name) = "raider #{command} #{name} schedule"
|
|
55
|
+
|
|
56
|
+
def not_ours = "not a #{noun}"
|
|
57
|
+
|
|
58
|
+
def stranded(name) = "This schedule is a systemd timer; run #{stranded_hint || "#{remove_command} #{name}"} from a session where systemctl --user works"
|
|
59
|
+
|
|
60
|
+
def unsupported(host_os) = "Scheduling #{plural} is not supported on #{host_os}"
|
|
61
|
+
|
|
62
|
+
# The schedules Ruby Raider 4.1 made (`raider flow schedule`), only ever removed now, by the cleanup every
|
|
63
|
+
# `raider schedule` command runs first (Legacy). The words the user reads never say flow; the command
|
|
64
|
+
# (os_id prefix, cron tag, the line under it) is what 4.1 wrote.
|
|
65
|
+
# remove_command is never printed for it: stranded_hint overrides it in stranded (list takes no name).
|
|
66
|
+
FLOW_LEGACY = new(command: 'flow', dir: '.raider/flows', noun: '4.1 schedule', plural: '4.1 schedules',
|
|
67
|
+
remove_command: 'raider schedule list', stranded_hint: 'raider schedule list', reserved: %w[env])
|
|
68
|
+
|
|
69
|
+
# The schedules of `raider schedule`: the project's tests, all, one tag or listed spec files
|
|
70
|
+
TESTS = new(command: 'schedule', dir: '.raider/schedules', noun: 'test schedule', plural: 'tests',
|
|
71
|
+
remove_command: 'raider schedule remove')
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|