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.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/e2e_tests.yml +3 -0
  3. data/README.md +52 -53
  4. data/RELEASE.md +14 -1
  5. data/lib/commands/schedule_commands.rb +84 -0
  6. data/lib/generators/templates/common/git_ignore.tt +1 -0
  7. data/lib/ruby_raider/runtime/retry_reset.rb +32 -0
  8. data/lib/ruby_raider/runtime/rspec.rb +1 -0
  9. data/lib/ruby_raider/runtime/show_browser.rb +53 -0
  10. data/lib/ruby_raider/runtime.rb +2 -0
  11. data/lib/ruby_raider.rb +22 -7
  12. data/lib/utilities/schedules/adapter.rb +22 -0
  13. data/lib/utilities/schedules/base.rb +27 -0
  14. data/lib/utilities/schedules/kind.rb +74 -0
  15. data/lib/utilities/schedules/legacy.rb +196 -0
  16. data/lib/utilities/schedules/legacy_stub.rb +62 -0
  17. data/lib/utilities/{flows → schedules}/next_run.rb +2 -2
  18. data/lib/utilities/{flows → schedules}/notifier.rb +6 -6
  19. data/lib/utilities/{flows → schedules}/os/command_runner.rb +1 -1
  20. data/lib/utilities/{flows → schedules}/os/cron.rb +27 -17
  21. data/lib/utilities/{flows → schedules}/os/launchd.rb +32 -14
  22. data/lib/utilities/{flows → schedules}/os/linux.rb +23 -5
  23. data/lib/utilities/{flows → schedules}/os/systemd.rb +33 -17
  24. data/lib/utilities/{flows → schedules}/os/task_scheduler.rb +36 -20
  25. data/lib/utilities/schedules/os_enumerator.rb +30 -0
  26. data/lib/utilities/schedules/project.rb +70 -0
  27. data/lib/utilities/schedules/rule.rb +162 -0
  28. data/lib/utilities/schedules/run_lock.rb +160 -0
  29. data/lib/utilities/schedules/run_log.rb +122 -0
  30. data/lib/utilities/schedules/run_report.rb +139 -0
  31. data/lib/utilities/schedules/run_wrapper.rb +216 -0
  32. data/lib/utilities/schedules/schedule.rb +89 -0
  33. data/lib/utilities/schedules/schedule_store.rb +53 -0
  34. data/lib/utilities/schedules/scheduler.rb +178 -0
  35. data/lib/utilities/schedules/scheduler_steps.rb +69 -0
  36. data/lib/utilities/schedules/selection.rb +133 -0
  37. data/lib/utilities/schedules/spawn.rb +68 -0
  38. data/lib/utilities/{flows/schedule_store.rb → schedules/store.rb} +17 -13
  39. data/lib/version +1 -1
  40. data/ruby_raider.gemspec +1 -0
  41. data/sig/commands/schedule_commands.rbs +12 -0
  42. data/sig/ruby_raider/runtime/retry_reset.rbs +7 -0
  43. data/sig/ruby_raider/runtime/show_browser.rbs +27 -0
  44. data/sig/ruby_raider.rbs +4 -0
  45. data/sig/utilities/schedules/adapter.rbs +5 -0
  46. data/sig/utilities/schedules/base.rbs +15 -0
  47. data/sig/utilities/schedules/kind.rbs +32 -0
  48. data/sig/utilities/schedules/legacy.rbs +45 -0
  49. data/sig/utilities/schedules/legacy_stub.rbs +15 -0
  50. data/sig/utilities/{flows → schedules}/next_run.rbs +3 -3
  51. data/sig/utilities/{flows → schedules}/notifier.rbs +1 -1
  52. data/sig/utilities/{flows → schedules}/os/command_runner.rbs +1 -1
  53. data/sig/utilities/{flows → schedules}/os/cron.rbs +7 -7
  54. data/sig/utilities/{flows → schedules}/os/launchd.rbs +7 -6
  55. data/sig/utilities/{flows → schedules}/os/linux.rbs +10 -4
  56. data/sig/utilities/{flows → schedules}/os/systemd.rbs +8 -7
  57. data/sig/utilities/{flows → schedules}/os/task_scheduler.rbs +13 -10
  58. data/sig/utilities/schedules/os_enumerator.rbs +12 -0
  59. data/sig/utilities/schedules/project.rbs +22 -0
  60. data/sig/utilities/{flows/schedule.rbs → schedules/rule.rbs} +23 -12
  61. data/sig/utilities/schedules/run_lock.rbs +29 -0
  62. data/sig/utilities/schedules/run_log.rbs +33 -0
  63. data/sig/utilities/schedules/run_report.rbs +34 -0
  64. data/sig/utilities/schedules/run_wrapper.rbs +45 -0
  65. data/sig/utilities/schedules/schedule.rbs +27 -0
  66. data/sig/utilities/schedules/schedule_store.rbs +21 -0
  67. data/sig/utilities/{flows → schedules}/scheduler.rbs +22 -15
  68. data/sig/utilities/schedules/scheduler_steps.rbs +22 -0
  69. data/sig/utilities/schedules/selection.rbs +36 -0
  70. data/sig/utilities/schedules/spawn.rbs +10 -0
  71. data/sig/utilities/{flows/schedule_store.rbs → schedules/store.rbs} +14 -9
  72. data/spec/commands/schedule_commands_spec.rb +284 -0
  73. data/spec/integration/content/common_content_spec.rb +8 -2
  74. data/spec/integration/content/gemfile_content_spec.rb +2 -2
  75. data/spec/integration/end_to_end_spec.rb +0 -63
  76. data/spec/runtime/retry_reset_spec.rb +112 -0
  77. data/spec/runtime/show_browser_project_spec.rb +70 -0
  78. data/spec/runtime/show_browser_spec.rb +101 -0
  79. data/spec/utilities/schedules/kind_spec.rb +81 -0
  80. data/spec/utilities/schedules/legacy_spec.rb +433 -0
  81. data/spec/utilities/schedules/legacy_stub_spec.rb +144 -0
  82. data/spec/utilities/{flows → schedules}/next_run_spec.rb +5 -5
  83. data/spec/utilities/{flows → schedules}/notifier_spec.rb +3 -4
  84. data/spec/utilities/{flows → schedules}/os/command_runner_spec.rb +2 -3
  85. data/spec/utilities/{flows → schedules}/os/cron_spec.rb +34 -29
  86. data/spec/utilities/{flows → schedules}/os/launchd_spec.rb +42 -29
  87. data/spec/utilities/{flows → schedules}/os/linux_spec.rb +27 -13
  88. data/spec/utilities/schedules/os/list_ids_spec.rb +202 -0
  89. data/spec/utilities/{flows → schedules}/os/systemd_spec.rb +35 -31
  90. data/spec/utilities/{flows → schedules}/os/task_scheduler_spec.rb +40 -34
  91. data/spec/utilities/schedules/os_entry_spec.rb +54 -0
  92. data/spec/utilities/schedules/os_enumerator_spec.rb +57 -0
  93. data/spec/utilities/schedules/project_spec.rb +125 -0
  94. data/spec/utilities/schedules/run_lock_spec.rb +143 -0
  95. data/spec/utilities/schedules/run_log_spec.rb +83 -0
  96. data/spec/utilities/schedules/run_report_spec.rb +150 -0
  97. data/spec/utilities/schedules/run_wrapper_integration_spec.rb +166 -0
  98. data/spec/utilities/schedules/run_wrapper_spec.rb +430 -0
  99. data/spec/utilities/schedules/schedule_spec.rb +120 -0
  100. data/spec/utilities/schedules/schedule_store_spec.rb +151 -0
  101. data/spec/utilities/schedules/scheduler_launchd_spec.rb +77 -0
  102. data/spec/utilities/schedules/scheduler_spec.rb +445 -0
  103. data/spec/utilities/schedules/selection_spec.rb +176 -0
  104. data/spec/utilities/schedules/spawn_spec.rb +104 -0
  105. metadata +106 -53
  106. data/lib/commands/flow_commands.rb +0 -104
  107. data/lib/generators/templates/flows/flow_helper.tt +0 -42
  108. data/lib/generators/templates/flows/run.tt +0 -332
  109. data/lib/utilities/flows/project.rb +0 -170
  110. data/lib/utilities/flows/run_log.rb +0 -61
  111. data/lib/utilities/flows/runner.rb +0 -206
  112. data/lib/utilities/flows/schedule.rb +0 -221
  113. data/lib/utilities/flows/scheduler.rb +0 -187
  114. data/sig/commands/flow_commands.rbs +0 -13
  115. data/sig/utilities/flows/project.rbs +0 -46
  116. data/sig/utilities/flows/run_log.rbs +0 -22
  117. data/sig/utilities/flows/runner.rbs +0 -35
  118. data/spec/commands/flow_commands_spec.rb +0 -128
  119. data/spec/generators/flow_helper_spec.rb +0 -40
  120. data/spec/integration/flows/flow_project.rb +0 -62
  121. data/spec/integration/flows/run_command_spec.rb +0 -91
  122. data/spec/integration/flows/run_lifecycle_spec.rb +0 -383
  123. data/spec/utilities/flows/project_spec.rb +0 -178
  124. data/spec/utilities/flows/run_log_spec.rb +0 -47
  125. data/spec/utilities/flows/runner_spec.rb +0 -313
  126. data/spec/utilities/flows/schedule_spec.rb +0 -233
  127. data/spec/utilities/flows/schedule_store_spec.rb +0 -137
  128. data/spec/utilities/flows/scheduler_spec.rb +0 -329
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'open3'
5
+ require 'rbconfig'
6
+ require 'tmpdir'
7
+
8
+ # Real rspec-retry runs in a subprocess, no browser. The suite mimics a generated project: driver_helper
9
+ # memoises @driver, the after hook quits it, and a quit driver refuses every call like a dead session.
10
+ RSpec.describe 'Retrying a test with a fresh driver' do
11
+ lib = File.expand_path('../../lib', __dir__)
12
+
13
+ suite = <<~'RUBY'
14
+ require 'json'
15
+ require 'rspec'
16
+ require 'rspec/retry' if ENV['RETRY'] == 'before'
17
+ require 'ruby_raider/runtime/rspec'
18
+ require 'rspec/retry' if ENV['RETRY'] == 'after'
19
+
20
+ LOG = ENV.fetch('LOG')
21
+ def log(entry) = File.open(LOG, 'a') { |file| file.puts(JSON.generate(entry)) }
22
+
23
+ class FakeDriver
24
+ def quit = @quit = true
25
+
26
+ def title
27
+ raise Errno::ECONNREFUSED, '127.0.0.1:9515' if @quit
28
+
29
+ 'ok'
30
+ end
31
+ end
32
+
33
+ module DriverHelper
34
+ def driver = @driver ||= FakeDriver.new
35
+ end
36
+
37
+ RSpec.configure do |config|
38
+ config.include(DriverHelper)
39
+ config.default_retry_count = ENV.fetch('RETRY_COUNT', 0).to_i if config.respond_to?(:default_retry_count=)
40
+ # A project's own callback: one that resets the driver itself, or one that does not
41
+ if ENV['OWN_CALLBACK']
42
+ config.retry_callback = proc do
43
+ log('callback' => 'project')
44
+ @driver = nil if ENV['OWN_CALLBACK'] == 'resets'
45
+ end
46
+ end
47
+ config.before(:each) { driver.title }
48
+ config.after(:each) { driver.quit }
49
+ config.after(:suite) do
50
+ callback = config.retry_callback if config.respond_to?(:retry_callback)
51
+ owner = { Raider::RetryReset::CALLBACK => 'ours', nil => 'none' }.fetch(callback, 'project')
52
+ log('installed' => config.respond_to?(:retry_callback) ? owner : 'no rspec-retry')
53
+ end
54
+ end
55
+
56
+ RSpec.describe 'Flaky' do
57
+ it 'fails on its first try and passes on the next' do
58
+ tries = File.readlines(LOG).count { |line| line.include?('"try"') } + 1
59
+ log('try' => tries, 'driver' => driver.object_id)
60
+ expect(tries).to eq(ENV.fetch('PASS_ON', '2').to_i)
61
+ end
62
+ end
63
+ RUBY
64
+
65
+ # The subprocess is a plain ruby, not bundle exec: it finds rspec and rspec-retry through the bundle env
66
+ # (BUNDLE_GEMFILE, RUBYOPT=-rbundler/setup) it inherits from this rspec run.
67
+ define_method(:run_suite) do |env|
68
+ Dir.mktmpdir('raider-retry') do |dir|
69
+ spec = File.join(dir, 'flaky_spec.rb')
70
+ log = File.join(dir, 'log.jsonl')
71
+ File.write(spec, suite)
72
+ File.write(log, '')
73
+ out, err, status = Open3.capture3(env.merge('LOG' => log, 'RSPEC_RETRY_RETRY_COUNT' => nil), RbConfig.ruby, '-I', lib,
74
+ '-e', 'require "rspec/core"; exit RSpec::Core::Runner.run(ARGV)', spec, chdir: dir)
75
+ { status: status.exitstatus, output: out + err, log: File.readlines(log).map { |line| JSON.parse(line) } }
76
+ end
77
+ end
78
+
79
+ define_method(:tries) { |result| result[:log].select { |entry| entry.key?('try') } }
80
+ define_method(:installed) { |result| result[:log].find { |entry| entry.key?('installed') }&.fetch('installed') }
81
+
82
+ %w[before after].each do |order|
83
+ it "passes on the second try with a fresh driver when rspec/retry is required #{order} the runtime" do
84
+ result = run_suite('RETRY' => order, 'RETRY_COUNT' => '2')
85
+ expect([result[:status], tries(result).map { |entry| entry['try'] }]).to eq([0, [1, 2]]), result[:output]
86
+ expect(tries(result).map { |entry| entry['driver'] }.uniq.size).to eq(2)
87
+ expect(installed(result)).to eq('ours')
88
+ end
89
+ end
90
+
91
+ it 'keeps a retry_callback the project set' do
92
+ result = run_suite('RETRY' => 'before', 'RETRY_COUNT' => '2', 'OWN_CALLBACK' => 'resets')
93
+ expect([result[:status], installed(result)]).to eq([0, 'project']), result[:output]
94
+ expect(result[:log]).to include({ 'callback' => 'project' })
95
+ end
96
+
97
+ it 'a project callback that keeps the driver still fails on the quit session' do
98
+ result = run_suite('RETRY' => 'before', 'RETRY_COUNT' => '2', 'OWN_CALLBACK' => 'keeps')
99
+ expect([result[:status], installed(result), tries(result).size]).to eq([1, 'project', 1])
100
+ expect(result[:output]).to include('Errno::ECONNREFUSED')
101
+ end
102
+
103
+ it 'changes nothing when no retry happens' do
104
+ result = run_suite('RETRY' => 'before', 'RETRY_COUNT' => '2', 'PASS_ON' => '1')
105
+ expect([result[:status], tries(result).size, installed(result)]).to eq([0, 1, 'ours']), result[:output]
106
+ end
107
+
108
+ it 'does nothing without rspec-retry' do
109
+ result = run_suite('PASS_ON' => '1')
110
+ expect([result[:status], installed(result)]).to eq([0, 'no rspec-retry']), result[:output]
111
+ end
112
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'json'
5
+ require 'open3'
6
+ require 'rbconfig'
7
+ require 'rspec'
8
+ require 'tmpdir'
9
+
10
+ # A Selenium project generated by this branch (its helper templates are byte-identical to 4.0.1's) with
11
+ # `headless: true` in config/config.yml. Its own helpers/driver_helper.rb builds the browser options, no
12
+ # browser is started. RAIDER_SHOW_BROWSER=1 drops the --headless it adds; config.yml is never written.
13
+ RSpec.describe 'Show the browser in a generated project', :slow do
14
+ lib = File.expand_path('../../lib', __dir__)
15
+
16
+ generate = <<~RUBY
17
+ require 'generators/invoke_generators'
18
+ InvokeGenerators.generate_framework(automation: 'selenium', framework: 'rspec', name: 'shown', examples: false)
19
+ RUBY
20
+
21
+ # Loads the project's real driver_helper and prints the args of the options it builds
22
+ options = <<~RUBY
23
+ require 'json'
24
+ require './helpers/driver_helper'
25
+ helper = Object.new.extend(DriverHelper)
26
+ helper.instance_variable_set(:@config, YAML.load_file('config/config.yml'))
27
+ built = helper.send(:create_webdriver_options)
28
+ puts JSON.generate(built.as_json.fetch(built.class::KEY).fetch('args'))
29
+ RUBY
30
+
31
+ define_method(:ruby) do |env, dir, script|
32
+ out, err, status = Open3.capture3(env, RbConfig.ruby, '-I', lib, '-e', script, chdir: dir)
33
+ raise "ruby failed in #{dir}: #{err}" unless status.success?
34
+
35
+ out
36
+ end
37
+
38
+ # rubocop:disable RSpec/InstanceVariable
39
+ before(:all) do # rubocop:disable RSpec/BeforeAfterAll
40
+ @tmp = Dir.mktmpdir('raider-show-browser')
41
+ ruby({}, @tmp, generate)
42
+ @project = File.join(@tmp, 'shown')
43
+ config = File.join(@project, 'config', 'config.yml')
44
+ File.write(config, "#{File.read(config)}headless: true\n")
45
+ @before = File.binread(config)
46
+ @args = { off: { 'RAIDER_SHOW_BROWSER' => nil, 'HEADLESS' => nil }, shown: { 'RAIDER_SHOW_BROWSER' => '1', 'HEADLESS' => nil },
47
+ shown_hidden_env: { 'RAIDER_SHOW_BROWSER' => '1', 'HEADLESS' => 'true' }, other: { 'RAIDER_SHOW_BROWSER' => 'true', 'HEADLESS' => nil } }
48
+ .transform_values { |env| JSON.parse(ruby(env, @project, options)) }
49
+ end
50
+
51
+ after(:all) { FileUtils.rm_rf(@tmp) } # rubocop:disable RSpec/BeforeAfterAll
52
+
53
+ it 'requires the runtime from the generated driver_helper' do
54
+ expect(File.read(File.join(@project, 'helpers', 'driver_helper.rb'))).to include("require 'ruby_raider/runtime'")
55
+ end
56
+
57
+ it 'keeps --headless without RAIDER_SHOW_BROWSER=1' do
58
+ expect(@args.values_at(:off, :other)).to all(include('--headless'))
59
+ end
60
+
61
+ it 'drops --headless with RAIDER_SHOW_BROWSER=1, even with HEADLESS set, and keeps the browser arguments' do
62
+ expected = @args[:off] - ['--headless']
63
+ expect(@args.values_at(:shown, :shown_hidden_env)).to eq([expected, expected])
64
+ end
65
+
66
+ it 'never writes config.yml' do
67
+ expect(File.binread(File.join(@project, 'config', 'config.yml'))).to eq(@before)
68
+ end
69
+ # rubocop:enable RSpec/InstanceVariable
70
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'open3'
5
+ require 'rbconfig'
6
+ require 'rspec'
7
+ require_relative '../../lib/ruby_raider/runtime/show_browser'
8
+
9
+ # The prepend is process-wide, so every case that loads the runtime and looks at the Options classes runs
10
+ # in its own ruby with an explicit RAIDER_SHOW_BROWSER. This process only checks the pure helpers.
11
+ RSpec.describe Raider::ShowBrowser do # rubocop:disable RSpec/FilePath
12
+ lib = File.expand_path('../../lib', __dir__)
13
+
14
+ # Loads the runtime the way a project's driver_helper does, then reports the real Options classes
15
+ probe = <<~RUBY
16
+ require 'json'
17
+ require 'ruby_raider/runtime'
18
+ filter = Raider::ShowBrowser::Filter
19
+ Raider::ShowBrowser.install(ENV) if ARGV.include?('again')
20
+ report = %w[Chrome Edge Firefox].to_h do |browser|
21
+ klass = Selenium::WebDriver.const_get(browser)::Options
22
+ built = klass.new(args: ['--headless', 'start-maximized', '--headless=new', '-headless'])
23
+ %w[--headless --headless=old -headless window-size=800,600 --headlessly headless].each { |arg| built.add_argument(arg) }
24
+ set = klass.new
25
+ set.args = ['--headless', 'set']
26
+ keyed = klass.new('args' => ['-headless', 'keyed'])
27
+ args = ->(options) { options.as_json.fetch(klass::KEY).fetch('args') }
28
+ added = klass.new
29
+ returned = [added.add_argument('--headless'), added.add_argument('kept')].map { |value| value.equal?(added.options[:args]) }
30
+ [browser, { 'prepended' => klass.ancestors.count(filter), 'built' => args.call(built), 'set' => args.call(set),
31
+ 'keyed' => keyed.options['args'], 'returned' => returned }]
32
+ end
33
+ puts JSON.generate(report)
34
+ RUBY
35
+
36
+ define_method(:probe) do |value, *argv|
37
+ out, err, status = Open3.capture3({ 'RAIDER_SHOW_BROWSER' => value, 'HEADLESS' => nil }, RbConfig.ruby, '-I', lib, '-e', probe, *argv)
38
+ raise "probe failed: #{err}" unless status.success?
39
+
40
+ JSON.parse(out)
41
+ end
42
+
43
+ context 'with RAIDER_SHOW_BROWSER=1' do
44
+ let(:report) { probe('1') }
45
+
46
+ it 'prepends the filter once to the Chrome, Edge and Firefox options' do
47
+ expect(report.transform_values { |browser| browser['prepended'] }).to eq('Chrome' => 1, 'Edge' => 1, 'Firefox' => 1)
48
+ end
49
+
50
+ it 'prepends it once when installed again' do
51
+ expect(probe('1', 'again').transform_values { |browser| browser['prepended'] }).to eq('Chrome' => 1, 'Edge' => 1, 'Firefox' => 1)
52
+ end
53
+
54
+ it 'drops --headless, --headless=* and -headless from args: and add_argument and keeps every other argument' do
55
+ expected = %w[start-maximized window-size=800,600 --headlessly headless]
56
+ expect(report.transform_values { |browser| browser['built'] }).to eq('Chrome' => expected, 'Edge' => expected, 'Firefox' => expected)
57
+ end
58
+
59
+ it 'returns the args from add_argument as Selenium does, also for a dropped switch' do
60
+ expect(report.transform_values { |browser| browser['returned'] }).to eq('Chrome' => [true, true], 'Edge' => [true, true], 'Firefox' => [true, true])
61
+ end
62
+
63
+ it 'drops them from args= and from a string args key' do
64
+ expect(report.transform_values { |browser| browser.values_at('set', 'keyed') })
65
+ .to eq('Chrome' => [%w[set], %w[keyed]], 'Edge' => [%w[set], %w[keyed]], 'Firefox' => [%w[set], %w[keyed]])
66
+ end
67
+ end
68
+
69
+ [nil, '', '0', 'true', '1 ', 'yes'].each do |value|
70
+ it "prepends nothing and keeps --headless when RAIDER_SHOW_BROWSER is #{value.inspect}" do
71
+ report = probe(value)
72
+ expect(report.transform_values { |browser| [browser['prepended'], browser['built'].first, browser['set']] })
73
+ .to eq('Chrome' => [0, '--headless', %w[--headless set]], 'Edge' => [0, '--headless', %w[--headless set]],
74
+ 'Firefox' => [0, '--headless', %w[--headless set]])
75
+ end
76
+ end
77
+
78
+ describe '.headless?' do
79
+ it 'matches only the headless switches' do
80
+ args = ['--headless', '--headless=new', '--headless=', '-headless', 'headless', '--headlessly', '-headless=new', ' --headless', :'--headless', nil]
81
+ expect(args.select { |arg| described_class.headless?(arg) }).to eq(['--headless', '--headless=new', '--headless=', '-headless'])
82
+ end
83
+ end
84
+
85
+ describe '.without_headless' do
86
+ it 'returns a new array and leaves the given one untouched' do
87
+ given = %w[--headless keep].freeze
88
+ expect(described_class.without_headless(given)).to eq(%w[keep])
89
+ end
90
+
91
+ it 'passes anything that is not an array through' do
92
+ expect([nil, '--headless'].map { |value| described_class.without_headless(value) }).to eq([nil, '--headless'])
93
+ end
94
+ end
95
+
96
+ describe '.install' do
97
+ it 'installs nothing unless the value is exactly 1' do
98
+ expect([{}, { 'RAIDER_SHOW_BROWSER' => 'true' }].map { |env| described_class.install(env) }).to eq([[], []])
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../lib/utilities/schedules/kind'
4
+
5
+ RSpec.describe RubyRaider::Schedules::Kind do # rubocop:disable RSpec/FilePath
6
+ let(:kind) { described_class::FLOW_LEGACY }
7
+ let(:os_id) { 'dev.rubyraider.flow.0123456789.report' }
8
+
9
+ it 'keeps the 4.1 words in its OS entries and says 4.1 schedule to the user' do
10
+ expect([kind.os_id_prefix, kind.cron_tag, kind.dir, kind.task_file_prefix, kind.service_description('report'),
11
+ kind.timer_description('report'), kind.task_description('report', 'every day at 08:00'), kind.not_ours,
12
+ kind.unsupported('solaris')])
13
+ .to eq(['dev.rubyraider.flow', '# raider-flow ', '.raider/flows', 'raider-flow-task', 'raider flow report',
14
+ 'raider flow report schedule', 'Ruby Raider flow report (every day at 08:00)', 'not a 4.1 schedule',
15
+ 'Scheduling 4.1 schedules is not supported on solaris'])
16
+ end
17
+
18
+ it 'names raider schedule list, with no name, for a stranded timer' do
19
+ expect(kind.stranded('report'))
20
+ .to eq('This schedule is a systemd timer; run raider schedule list from a session where systemctl --user works')
21
+ end
22
+
23
+ it 'says no flow word to the user' do
24
+ expect([kind.noun, kind.plural, kind.remove_command, kind.not_ours, kind.stranded('report'), kind.unsupported('solaris')])
25
+ .to all(satisfy { |text| !text.match?(/\bflows?\b/i) })
26
+ end
27
+
28
+ it 'reads the name from an os_id of its own kind only' do
29
+ expect([kind.name_in(os_id), kind.name_in('dev.rubyraider.schedule.0123456789.report'), kind.name_in("#{os_id}\n"),
30
+ kind.name_in('dev.rubyraider.flow.0123456789.env'), kind.name_in(nil)]).to eq(['report', nil, nil, nil, nil])
31
+ end
32
+
33
+ it 'reserves env for flows while Schedules does not' do
34
+ expect([kind.valid_name?('env'), RubyRaider::Schedules.valid_name?('env'), kind.valid_name?('a' * 61)]).to eq([false, true, false])
35
+ end
36
+
37
+ it 'knows its own command line under a cron tag' do
38
+ line = '0 8 * * * cd /p && /usr/bin/env /r /b exec raider flow run report --project /p --trigger schedule >> /l 2>&1'
39
+ expect([kind.our_command?(line, 'report'), kind.our_command?(line, 'other')]).to eq([true, false])
40
+ end
41
+
42
+ it 'is frozen' do
43
+ expect([kind, kind.os_id_prefix, kind.reserved]).to all(be_frozen)
44
+ end
45
+
46
+ describe 'TESTS' do
47
+ let(:tests) { described_class::TESTS }
48
+
49
+ it 'names test schedules with the schedule words' do
50
+ expect([tests.os_id_prefix, tests.cron_tag, tests.dir, tests.service_description('nightly'), tests.timer_description('nightly'),
51
+ tests.task_description('nightly', 'every day at 08:00'), tests.not_ours, tests.unsupported('solaris')])
52
+ .to eq(['dev.rubyraider.schedule', '# raider-schedule ', '.raider/schedules', 'raider schedule nightly',
53
+ 'raider schedule nightly schedule', 'Ruby Raider schedule nightly (every day at 08:00)', 'not a test schedule',
54
+ 'Scheduling tests is not supported on solaris'])
55
+ end
56
+
57
+ it 'names raider schedule remove for a stranded timer' do
58
+ expect(tests.stranded('nightly'))
59
+ .to eq('This schedule is a systemd timer; run raider schedule remove nightly from a session where systemctl --user works')
60
+ end
61
+
62
+ it 'reserves no name and reads only its own os_ids' do
63
+ expect([tests.valid_name?('env'), tests.name_in('dev.rubyraider.schedule.0123456789.env'), tests.name_in(os_id)]).to eq([true, 'env', nil])
64
+ end
65
+
66
+ it 'reads no os_id from text that is not valid UTF-8, and lists only ids under the prefix' do
67
+ prefix = 'dev.rubyraider.schedule.0123456789.'
68
+ broken = "#{prefix}a\xFF".dup.force_encoding(Encoding::UTF_8)
69
+
70
+ expect([tests.name_in(broken), tests.listed?(broken, prefix), tests.listed?("#{prefix}nightly", prefix),
71
+ tests.listed?("#{prefix}nightly", 'dev.rubyraider.schedule.9999999999.'), tests.listed?(nil, prefix)])
72
+ .to eq([nil, false, true, false, false])
73
+ end
74
+
75
+ it 'knows its own command line under a cron tag, not a flow one' do
76
+ line = '0 8 * * * cd /p && /usr/bin/env /r /b exec raider schedule run nightly --project /p --trigger schedule >> /l 2>&1'
77
+ expect([tests.our_command?(line, 'nightly'), kind.our_command?(line, 'nightly'), tests.our_command?(line.sub(' --trigger schedule', ''), 'nightly')])
78
+ .to eq([true, false, false])
79
+ end
80
+ end
81
+ end