wheneverd 0.1.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 +7 -0
- data/.gitignore +26 -0
- data/.rubocop.yml +41 -0
- data/.yardopts +8 -0
- data/AGENTS.md +42 -0
- data/CHANGELOG.md +28 -0
- data/FEATURE_SUMMARY.md +38 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +129 -0
- data/LICENSE +21 -0
- data/README.md +204 -0
- data/Rakefile +196 -0
- data/bin/console +8 -0
- data/bin/setup +5 -0
- data/exe/wheneverd +9 -0
- data/lib/wheneverd/cli/activate.rb +19 -0
- data/lib/wheneverd/cli/current.rb +22 -0
- data/lib/wheneverd/cli/deactivate.rb +19 -0
- data/lib/wheneverd/cli/delete.rb +20 -0
- data/lib/wheneverd/cli/help.rb +18 -0
- data/lib/wheneverd/cli/init.rb +78 -0
- data/lib/wheneverd/cli/reload.rb +40 -0
- data/lib/wheneverd/cli/show.rb +23 -0
- data/lib/wheneverd/cli/write.rb +32 -0
- data/lib/wheneverd/cli.rb +87 -0
- data/lib/wheneverd/core_ext/numeric_duration.rb +56 -0
- data/lib/wheneverd/dsl/at_normalizer.rb +48 -0
- data/lib/wheneverd/dsl/calendar_symbol_period_list.rb +42 -0
- data/lib/wheneverd/dsl/context.rb +72 -0
- data/lib/wheneverd/dsl/errors.rb +29 -0
- data/lib/wheneverd/dsl/loader.rb +49 -0
- data/lib/wheneverd/dsl/period_parser.rb +135 -0
- data/lib/wheneverd/duration.rb +27 -0
- data/lib/wheneverd/entry.rb +31 -0
- data/lib/wheneverd/errors.rb +9 -0
- data/lib/wheneverd/interval.rb +37 -0
- data/lib/wheneverd/job/command.rb +29 -0
- data/lib/wheneverd/schedule.rb +25 -0
- data/lib/wheneverd/systemd/calendar_spec.rb +109 -0
- data/lib/wheneverd/systemd/cron_parser.rb +352 -0
- data/lib/wheneverd/systemd/errors.rb +23 -0
- data/lib/wheneverd/systemd/renderer.rb +153 -0
- data/lib/wheneverd/systemd/systemctl.rb +38 -0
- data/lib/wheneverd/systemd/time_parser.rb +75 -0
- data/lib/wheneverd/systemd/unit_deleter.rb +64 -0
- data/lib/wheneverd/systemd/unit_lister.rb +59 -0
- data/lib/wheneverd/systemd/unit_namer.rb +69 -0
- data/lib/wheneverd/systemd/unit_writer.rb +132 -0
- data/lib/wheneverd/trigger/boot.rb +26 -0
- data/lib/wheneverd/trigger/calendar.rb +26 -0
- data/lib/wheneverd/trigger/interval.rb +30 -0
- data/lib/wheneverd/version.rb +6 -0
- data/lib/wheneverd.rb +41 -0
- data/test/cli_activate_test.rb +110 -0
- data/test/cli_current_test.rb +94 -0
- data/test/cli_deactivate_test.rb +111 -0
- data/test/cli_end_to_end_test.rb +98 -0
- data/test/cli_reload_test.rb +132 -0
- data/test/cli_systemctl_integration_test.rb +76 -0
- data/test/cli_systemd_analyze_test.rb +64 -0
- data/test/cli_test.rb +332 -0
- data/test/domain_model_test.rb +108 -0
- data/test/dsl_calendar_symbol_period_list_test.rb +53 -0
- data/test/dsl_loader_test.rb +384 -0
- data/test/support/cli_subprocess_test_helpers.rb +38 -0
- data/test/support/cli_test_helpers.rb +114 -0
- data/test/systemd_calendar_spec_test.rb +45 -0
- data/test/systemd_cron_parser_test.rb +114 -0
- data/test/systemd_renderer_errors_test.rb +85 -0
- data/test/systemd_renderer_test.rb +161 -0
- data/test/systemd_systemctl_test.rb +46 -0
- data/test/systemd_time_parser_test.rb +25 -0
- data/test/systemd_unit_deleter_test.rb +83 -0
- data/test/systemd_unit_writer_prune_test.rb +85 -0
- data/test/systemd_unit_writer_test.rb +71 -0
- data/test/test_helper.rb +34 -0
- data/test/wheneverd_test.rb +9 -0
- data/wheneverd.gemspec +35 -0
- metadata +136 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
require_relative "support/cli_test_helpers"
|
|
5
|
+
|
|
6
|
+
class CLIDeactivateSuccessTest < Minitest::Test
|
|
7
|
+
include CLITestHelpers
|
|
8
|
+
|
|
9
|
+
def test_exits_zero
|
|
10
|
+
with_inited_project_dir do
|
|
11
|
+
status, _out, _err, _calls = run_deactivate_with_capture3_stub
|
|
12
|
+
assert_equal 0, status
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_prints_timer_units
|
|
17
|
+
with_inited_project_dir do
|
|
18
|
+
status, out, err, _calls = run_deactivate_with_capture3_stub
|
|
19
|
+
assert_cli_success(status, err)
|
|
20
|
+
assert_includes out, expected_timer_basenames.fetch(0)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_runs_stop
|
|
25
|
+
with_inited_project_dir do
|
|
26
|
+
status, _out, err, calls = run_deactivate_with_capture3_stub
|
|
27
|
+
assert_cli_success(status, err)
|
|
28
|
+
assert_systemctl_call_starts_with(calls, 0, SYSTEMCTL_USER_PREFIX + ["stop"],
|
|
29
|
+
includes: expected_timer_basenames)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_runs_disable
|
|
34
|
+
with_inited_project_dir do
|
|
35
|
+
status, _out, err, calls = run_deactivate_with_capture3_stub
|
|
36
|
+
assert_cli_success(status, err)
|
|
37
|
+
assert_systemctl_call_starts_with(calls, 1, SYSTEMCTL_USER_PREFIX + ["disable"],
|
|
38
|
+
includes: expected_timer_basenames)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class CLIDeactivateScheduleMissingTest < Minitest::Test
|
|
44
|
+
include CLITestHelpers
|
|
45
|
+
|
|
46
|
+
def test_exits_one
|
|
47
|
+
with_project_dir do
|
|
48
|
+
status, _out, _err = run_cli(["deactivate"])
|
|
49
|
+
assert_equal 1, status
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_prints_error_message
|
|
54
|
+
with_project_dir do
|
|
55
|
+
_status, out, err = run_cli(["deactivate"])
|
|
56
|
+
assert_equal "", out
|
|
57
|
+
assert_includes err, "Schedule file not found"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class CLIDeactivateEmptyScheduleTest < Minitest::Test
|
|
63
|
+
include CLITestHelpers
|
|
64
|
+
|
|
65
|
+
def test_makes_no_systemctl_calls
|
|
66
|
+
with_project_dir do
|
|
67
|
+
write_empty_schedule
|
|
68
|
+
status, _out, err, calls = run_deactivate_with_capture3_stub
|
|
69
|
+
assert_cli_success(status, err)
|
|
70
|
+
assert_equal [], calls
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_prints_nothing
|
|
75
|
+
with_project_dir do
|
|
76
|
+
write_empty_schedule
|
|
77
|
+
status, out, err, _calls = run_deactivate_with_capture3_stub
|
|
78
|
+
assert_cli_success(status, err)
|
|
79
|
+
assert_equal "", out
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class CLIDeactivateSystemctlFailureTest < Minitest::Test
|
|
85
|
+
include CLITestHelpers
|
|
86
|
+
|
|
87
|
+
def test_exits_one
|
|
88
|
+
with_inited_project_dir do
|
|
89
|
+
status, _out, _err, _calls = run_deactivate_with_capture3_stub(exitstatus: 1,
|
|
90
|
+
stderr: "no bus\n")
|
|
91
|
+
assert_equal 1, status
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_prints_systemctl_failed
|
|
96
|
+
with_inited_project_dir do
|
|
97
|
+
_status, _out, err, _calls = run_deactivate_with_capture3_stub(exitstatus: 1,
|
|
98
|
+
stderr: "no bus\n")
|
|
99
|
+
assert_includes err, "systemctl failed"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def test_only_calls_stop
|
|
104
|
+
with_inited_project_dir do
|
|
105
|
+
_status, _out, _err, calls = run_deactivate_with_capture3_stub(exitstatus: 1,
|
|
106
|
+
stderr: "no bus\n")
|
|
107
|
+
assert_equal 1, calls.length
|
|
108
|
+
assert_systemctl_call_starts_with(calls, 0, SYSTEMCTL_USER_PREFIX + ["stop"])
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
require_relative "support/cli_subprocess_test_helpers"
|
|
5
|
+
|
|
6
|
+
class CLIEndToEndTest < Minitest::Test
|
|
7
|
+
include CLISubprocessTestHelpers
|
|
8
|
+
|
|
9
|
+
def test_init_show_write_current_delete_workflow_via_exe
|
|
10
|
+
with_temp_project_dir do |project_dir|
|
|
11
|
+
init_schedule(project_dir)
|
|
12
|
+
show_units(project_dir)
|
|
13
|
+
unit_dir = File.join(project_dir, "tmp_units")
|
|
14
|
+
write_units(project_dir, unit_dir)
|
|
15
|
+
current_units(project_dir, unit_dir)
|
|
16
|
+
delete_units(project_dir, unit_dir)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def init_schedule(project_dir)
|
|
23
|
+
status, out, err = run_exe(["init"], chdir: project_dir)
|
|
24
|
+
assert_equal 0, status
|
|
25
|
+
assert_equal "", err
|
|
26
|
+
assert_includes out, "Wrote schedule template"
|
|
27
|
+
assert File.file?(File.join(project_dir, "config", "schedule.rb"))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def show_units(project_dir)
|
|
31
|
+
status, out, err = run_exe(["show", "--identifier", "demo"], chdir: project_dir)
|
|
32
|
+
assert_equal 0, status
|
|
33
|
+
assert_equal "", err
|
|
34
|
+
assert_includes out, "OnActiveSec=300"
|
|
35
|
+
assert_includes out, "OnUnitActiveSec=300"
|
|
36
|
+
assert_includes out, "ExecStart=echo hello"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def write_units(project_dir, unit_dir)
|
|
40
|
+
status, out, err = run_exe(
|
|
41
|
+
["write", "--identifier", "demo", "--unit-dir", unit_dir],
|
|
42
|
+
chdir: project_dir
|
|
43
|
+
)
|
|
44
|
+
assert_equal 0, status
|
|
45
|
+
assert_equal "", err
|
|
46
|
+
assert_timer_unit_written(unit_dir, out)
|
|
47
|
+
assert_timer_unit_contents(unit_dir)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def current_units(project_dir, unit_dir)
|
|
51
|
+
status, out, err = run_exe(
|
|
52
|
+
["current", "--identifier", "demo", "--unit-dir", unit_dir],
|
|
53
|
+
chdir: project_dir
|
|
54
|
+
)
|
|
55
|
+
assert_equal 0, status
|
|
56
|
+
assert_equal "", err
|
|
57
|
+
assert_match(/wheneverd-demo-[0-9a-f]{12}\.timer/, out)
|
|
58
|
+
assert_includes out, Wheneverd::Systemd::Renderer::MARKER_PREFIX
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def delete_units(project_dir, unit_dir)
|
|
62
|
+
status, out, err = run_exe(
|
|
63
|
+
["delete", "--identifier", "demo", "--unit-dir", unit_dir],
|
|
64
|
+
chdir: project_dir
|
|
65
|
+
)
|
|
66
|
+
assert_equal 0, status
|
|
67
|
+
assert_equal "", err
|
|
68
|
+
timer_path = out.lines.map(&:strip).find { |line| line.end_with?(".timer") }
|
|
69
|
+
assert timer_path, "expected delete to print at least one *.timer path"
|
|
70
|
+
assert_includes timer_path, unit_dir
|
|
71
|
+
refute File.exist?(timer_path)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def assert_timer_unit_written(unit_dir, out)
|
|
75
|
+
service_path = out.lines.map(&:strip).find { |line| line.end_with?(".service") }
|
|
76
|
+
timer_path = out.lines.map(&:strip).find { |line| line.end_with?(".timer") }
|
|
77
|
+
assert service_path, "expected write to print at least one *.service path"
|
|
78
|
+
assert timer_path, "expected write to print at least one *.timer path"
|
|
79
|
+
assert_includes service_path, unit_dir
|
|
80
|
+
assert_includes timer_path, unit_dir
|
|
81
|
+
assert File.exist?(timer_path)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def assert_timer_unit_contents(unit_dir)
|
|
85
|
+
timer_basenames = Dir.children(unit_dir).select { |b| b.end_with?(".timer") }
|
|
86
|
+
assert timer_basenames.any?, "expected unit_dir to contain at least one *.timer file"
|
|
87
|
+
|
|
88
|
+
timer_contents = timer_basenames.filter_map do |basename|
|
|
89
|
+
contents = File.read(File.join(unit_dir, basename))
|
|
90
|
+
contents if contents.include?("OnActiveSec=300")
|
|
91
|
+
end.first
|
|
92
|
+
|
|
93
|
+
assert timer_contents, "expected at least one interval timer with OnActiveSec=300"
|
|
94
|
+
assert_includes timer_contents, Wheneverd::Systemd::Renderer::MARKER_PREFIX
|
|
95
|
+
assert_includes timer_contents, "OnActiveSec=300"
|
|
96
|
+
assert_includes timer_contents, "OnUnitActiveSec=300"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
require_relative "support/cli_test_helpers"
|
|
5
|
+
|
|
6
|
+
class CLIReloadSuccessTest < Minitest::Test
|
|
7
|
+
include CLITestHelpers
|
|
8
|
+
|
|
9
|
+
def test_exits_zero
|
|
10
|
+
with_inited_project_dir do |project_dir|
|
|
11
|
+
unit_dir = File.join(project_dir, "tmp_units")
|
|
12
|
+
status, _out, err, _calls = run_reload_with_capture3_stub(unit_dir: unit_dir)
|
|
13
|
+
assert_cli_success(status, err)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_writes_unit_files
|
|
18
|
+
with_inited_project_dir do |project_dir|
|
|
19
|
+
unit_dir = File.join(project_dir, "tmp_units")
|
|
20
|
+
status, _out, err, _calls = run_reload_with_capture3_stub(unit_dir: unit_dir)
|
|
21
|
+
assert_cli_success(status, err)
|
|
22
|
+
assert File.exist?(File.join(unit_dir, expected_timer_basenames.fetch(0)))
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_runs_daemon_reload
|
|
27
|
+
with_inited_project_dir do |project_dir|
|
|
28
|
+
unit_dir = File.join(project_dir, "tmp_units")
|
|
29
|
+
status, _out, err, calls = run_reload_with_capture3_stub(unit_dir: unit_dir)
|
|
30
|
+
assert_cli_success(status, err)
|
|
31
|
+
assert_systemctl_call(calls, 0, SYSTEMCTL_USER_PREFIX + ["daemon-reload"])
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_runs_restart
|
|
36
|
+
with_inited_project_dir do |project_dir|
|
|
37
|
+
unit_dir = File.join(project_dir, "tmp_units")
|
|
38
|
+
status, _out, err, calls = run_reload_with_capture3_stub(unit_dir: unit_dir)
|
|
39
|
+
assert_cli_success(status, err)
|
|
40
|
+
assert_systemctl_call_starts_with(calls, 1, SYSTEMCTL_USER_PREFIX + ["restart"],
|
|
41
|
+
includes: expected_timer_basenames)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class CLIReloadScheduleMissingTest < Minitest::Test
|
|
47
|
+
include CLITestHelpers
|
|
48
|
+
|
|
49
|
+
def test_exits_one
|
|
50
|
+
with_project_dir do |project_dir|
|
|
51
|
+
unit_dir = File.join(project_dir, "tmp_units")
|
|
52
|
+
status, _out, _err = run_cli(["reload", "--unit-dir", unit_dir])
|
|
53
|
+
assert_equal 1, status
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_prints_error_message
|
|
58
|
+
with_project_dir do |project_dir|
|
|
59
|
+
unit_dir = File.join(project_dir, "tmp_units")
|
|
60
|
+
_status, out, err = run_cli(["reload", "--unit-dir", unit_dir])
|
|
61
|
+
assert_equal "", out
|
|
62
|
+
assert_includes err, "Schedule file not found"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class CLIReloadEmptyScheduleTest < Minitest::Test
|
|
68
|
+
include CLITestHelpers
|
|
69
|
+
|
|
70
|
+
def test_makes_no_systemctl_calls
|
|
71
|
+
with_project_dir do |project_dir|
|
|
72
|
+
unit_dir = File.join(project_dir, "tmp_units")
|
|
73
|
+
write_empty_schedule
|
|
74
|
+
status, _out, err, calls = run_reload_with_capture3_stub(unit_dir: unit_dir)
|
|
75
|
+
assert_cli_success(status, err)
|
|
76
|
+
assert_equal [], calls
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_prints_nothing
|
|
81
|
+
with_project_dir do |project_dir|
|
|
82
|
+
unit_dir = File.join(project_dir, "tmp_units")
|
|
83
|
+
write_empty_schedule
|
|
84
|
+
status, out, err, _calls = run_reload_with_capture3_stub(unit_dir: unit_dir)
|
|
85
|
+
assert_cli_success(status, err)
|
|
86
|
+
assert_equal "", out
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class CLIReloadSystemctlFailureTest < Minitest::Test
|
|
92
|
+
include CLITestHelpers
|
|
93
|
+
|
|
94
|
+
def test_exits_one
|
|
95
|
+
with_inited_project_dir do |project_dir|
|
|
96
|
+
unit_dir = File.join(project_dir, "tmp_units")
|
|
97
|
+
status, _out, _err, _calls = run_reload_with_capture3_stub(unit_dir: unit_dir,
|
|
98
|
+
exitstatus: 1,
|
|
99
|
+
stderr: "no bus\n")
|
|
100
|
+
assert_equal 1, status
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def test_prints_systemctl_failed
|
|
105
|
+
with_inited_project_dir do |project_dir|
|
|
106
|
+
unit_dir = File.join(project_dir, "tmp_units")
|
|
107
|
+
_status, _out, err, _calls = run_reload_with_capture3_stub(unit_dir: unit_dir,
|
|
108
|
+
exitstatus: 1,
|
|
109
|
+
stderr: "no bus\n")
|
|
110
|
+
assert_includes err, "systemctl failed"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def test_only_calls_daemon_reload
|
|
115
|
+
with_inited_project_dir do |project_dir|
|
|
116
|
+
unit_dir = File.join(project_dir, "tmp_units")
|
|
117
|
+
_status, _out, _err, calls = run_reload_with_capture3_stub(unit_dir: unit_dir,
|
|
118
|
+
exitstatus: 1,
|
|
119
|
+
stderr: "no bus\n")
|
|
120
|
+
assert_equal 1, calls.length
|
|
121
|
+
assert_systemctl_call(calls, 0, SYSTEMCTL_USER_PREFIX + ["daemon-reload"])
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def test_writes_units_before_systemctl_failure
|
|
126
|
+
with_inited_project_dir do |project_dir|
|
|
127
|
+
unit_dir = File.join(project_dir, "tmp_units")
|
|
128
|
+
run_reload_with_capture3_stub(unit_dir: unit_dir, exitstatus: 1, stderr: "no bus\n")
|
|
129
|
+
assert File.exist?(File.join(unit_dir, expected_timer_basenames.fetch(0)))
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
require_relative "support/cli_subprocess_test_helpers"
|
|
5
|
+
|
|
6
|
+
class CLISystemctlIntegrationTest < Minitest::Test
|
|
7
|
+
include CLISubprocessTestHelpers
|
|
8
|
+
|
|
9
|
+
def test_reload_invokes_systemctl_via_path_injection
|
|
10
|
+
with_temp_project_dir { |project_dir| assert_reload_invokes_systemctl(project_dir) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def assert_reload_invokes_systemctl(project_dir)
|
|
16
|
+
init_schedule(project_dir)
|
|
17
|
+
with_fake_systemctl(project_dir) do |env, log_path|
|
|
18
|
+
run_reload(project_dir, env)
|
|
19
|
+
assert_systemctl_log_includes_expected_calls(log_path)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def run_reload(project_dir, env)
|
|
24
|
+
unit_dir = File.join(project_dir, "tmp_units")
|
|
25
|
+
status, _out, err = run_exe(
|
|
26
|
+
["reload", "--identifier", "demo", "--unit-dir", unit_dir],
|
|
27
|
+
chdir: project_dir,
|
|
28
|
+
env: env
|
|
29
|
+
)
|
|
30
|
+
assert_equal 0, status
|
|
31
|
+
assert_equal "", err
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def init_schedule(project_dir)
|
|
35
|
+
status, out, err = run_exe(["init"], chdir: project_dir)
|
|
36
|
+
assert_equal 0, status
|
|
37
|
+
assert_equal "", err
|
|
38
|
+
assert_includes out, "Wrote schedule template"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def with_fake_systemctl(project_dir)
|
|
42
|
+
bin_dir = File.join(project_dir, "tmp_bin")
|
|
43
|
+
FileUtils.mkdir_p(bin_dir)
|
|
44
|
+
log_path = File.join(project_dir, "systemctl.log")
|
|
45
|
+
|
|
46
|
+
write_fake_systemctl(File.join(bin_dir, "systemctl"))
|
|
47
|
+
|
|
48
|
+
env = {
|
|
49
|
+
"PATH" => [bin_dir, ENV.fetch("PATH", "")].join(File::PATH_SEPARATOR),
|
|
50
|
+
"SYSTEMCTL_LOG" => log_path
|
|
51
|
+
}
|
|
52
|
+
yield env, log_path
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def write_fake_systemctl(path)
|
|
56
|
+
File.write(
|
|
57
|
+
path,
|
|
58
|
+
<<~RUBY
|
|
59
|
+
#!/usr/bin/env ruby
|
|
60
|
+
# frozen_string_literal: true
|
|
61
|
+
|
|
62
|
+
log_path = ENV.fetch("SYSTEMCTL_LOG")
|
|
63
|
+
File.open(log_path, "a") { |f| f.puts(ARGV.join(" ")) }
|
|
64
|
+
exit 0
|
|
65
|
+
RUBY
|
|
66
|
+
)
|
|
67
|
+
FileUtils.chmod(0o755, path)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def assert_systemctl_log_includes_expected_calls(log_path)
|
|
71
|
+
log = File.read(log_path)
|
|
72
|
+
assert_includes log, "--user --no-pager daemon-reload"
|
|
73
|
+
assert_includes log, "--user --no-pager restart"
|
|
74
|
+
assert_match(/wheneverd-demo-[0-9a-f]{12}\.timer/, log)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
require_relative "support/cli_subprocess_test_helpers"
|
|
5
|
+
|
|
6
|
+
class CLISystemdAnalyzeTest < Minitest::Test
|
|
7
|
+
include CLISubprocessTestHelpers
|
|
8
|
+
|
|
9
|
+
def test_rendered_on_calendar_values_parse_with_systemd_analyze_calendar
|
|
10
|
+
analyze = require_executable!("systemd-analyze")
|
|
11
|
+
true_bin = absolute_true_path || "true"
|
|
12
|
+
|
|
13
|
+
with_temp_project_dir { |project_dir| assert_calendars_ok(analyze, project_dir, true_bin) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def assert_calendars_ok(analyze, project_dir, true_bin)
|
|
19
|
+
write_schedule(project_dir, schedule_contents(true_bin))
|
|
20
|
+
status, out, err = run_exe(["show", "--identifier", "demo"], chdir: project_dir)
|
|
21
|
+
assert_equal 0, status
|
|
22
|
+
assert_equal "", err
|
|
23
|
+
|
|
24
|
+
values = on_calendar_values(out)
|
|
25
|
+
refute_empty values
|
|
26
|
+
values.each { |value| assert_systemd_analyze_calendar_ok(analyze, value) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def require_executable!(name)
|
|
30
|
+
path = find_executable(name)
|
|
31
|
+
skip "#{name} not available" unless path
|
|
32
|
+
path
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def schedule_contents(true_bin)
|
|
36
|
+
<<~RUBY
|
|
37
|
+
# frozen_string_literal: true
|
|
38
|
+
|
|
39
|
+
every :hour do
|
|
40
|
+
command "#{true_bin}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
every 1.day, at: "4:30 am" do
|
|
44
|
+
command "#{true_bin}"
|
|
45
|
+
end
|
|
46
|
+
RUBY
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def absolute_true_path
|
|
50
|
+
%w[/usr/bin/true /bin/true].find { |p| File.file?(p) && File.executable?(p) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def on_calendar_values(output)
|
|
54
|
+
output.lines
|
|
55
|
+
.grep(/\AOnCalendar=/)
|
|
56
|
+
.map { |line| line.delete_prefix("OnCalendar=").strip }
|
|
57
|
+
.uniq
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def assert_systemd_analyze_calendar_ok(analyze, value)
|
|
61
|
+
_stdout, stderr, status = Open3.capture3(analyze, "calendar", value)
|
|
62
|
+
assert_equal 0, status.exitstatus, stderr
|
|
63
|
+
end
|
|
64
|
+
end
|