modsvaskr 0.1.8 → 0.1.12

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.
@@ -1,44 +1,45 @@
1
- module Modsvaskr
2
-
3
- # Mixin adding logging functionality, both on screen and in file
4
- module Logger
5
-
6
- class << self
7
- attr_accessor :log_file
8
- attr_accessor :stdout_io
9
- end
10
- @log_file = File.expand_path('Modsvaskr.log')
11
- @stdout_io = $stdout
12
-
13
- # Log on screen and in log file
14
- #
15
- # Parameters::
16
- # * *msg* (String): Message to log
17
- def log(msg)
18
- complete_msg = "[ #{Time.now.strftime('%F %T')} ] - [ #{self.class.name.split('::').last} ] - #{msg}"
19
- Logger.stdout_io << "#{complete_msg}\n"
20
- File.open(Logger.log_file, 'a') do |f|
21
- f.puts complete_msg
22
- end
23
- end
24
-
25
- # Display an output to the user.
26
- # This is not a log.
27
- #
28
- # Parameters::
29
- # * *msg* (String): Message to output
30
- def out(msg)
31
- Logger.stdout_io << "#{msg}\n"
32
- end
33
-
34
- # Wait for the user to enter a line and hit Enter
35
- #
36
- # Result::
37
- # * String: The line entered by the user
38
- def wait_for_user_enter
39
- @config.no_prompt ? "\n" : $stdin.gets
40
- end
41
-
42
- end
43
-
44
- end
1
+ module Modsvaskr
2
+
3
+ # Mixin adding logging functionality, both on screen and in file
4
+ module Logger
5
+
6
+ class << self
7
+
8
+ attr_accessor :log_file, :stdout_io
9
+
10
+ end
11
+ @log_file = File.expand_path('Modsvaskr.log')
12
+ @stdout_io = $stdout
13
+
14
+ # Log on screen and in log file
15
+ #
16
+ # Parameters::
17
+ # * *msg* (String): Message to log
18
+ def log(msg)
19
+ complete_msg = "[ #{Time.now.strftime('%F %T')} ] - [ #{self.class.name.split('::').last} ] - #{msg}"
20
+ Logger.stdout_io << "#{complete_msg}\n"
21
+ File.open(Logger.log_file, 'a') do |f|
22
+ f.puts complete_msg
23
+ end
24
+ end
25
+
26
+ # Display an output to the user.
27
+ # This is not a log.
28
+ #
29
+ # Parameters::
30
+ # * *msg* (String): Message to output
31
+ def out(msg)
32
+ Logger.stdout_io << "#{msg}\n"
33
+ end
34
+
35
+ # Wait for the user to enter a line and hit Enter
36
+ #
37
+ # Result::
38
+ # * String: The line entered by the user
39
+ def wait_for_user_enter
40
+ @config.no_prompt ? "\n" : $stdin.gets
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -1,22 +1,23 @@
1
- module Modsvaskr
2
-
3
- module RunCmd
4
-
5
- # Run a given command with eventual parameters
6
- #
7
- # Parameters::
8
- # * *cmd* (Hash<Symbol,Object>): The command description:
9
- # * *dir* (String): Directory in which the command is found
10
- # * *exe* (String): Name of the executable of the command
11
- # * *args* (Array<String>): Default arguments of the command [default = []]
12
- # * *args* (Array<String>): Additional arguments to give the command [default: []]
13
- def run_cmd(cmd, args: [])
14
- Dir.chdir cmd[:dir] do
15
- cmd_line = "\"#{cmd[:exe]}\" #{((cmd.key?(:args) ? cmd[:args] : []) + args).join(' ')}".strip
16
- raise "Unable to execute command line from \"#{cmd[:dir]}\": #{cmd_line}" unless system cmd_line
17
- end
18
- end
19
-
20
- end
21
-
22
- end
1
+ module Modsvaskr
2
+
3
+ # Common interface to execute commands in a robust way
4
+ module RunCmd
5
+
6
+ # Run a given command with eventual parameters
7
+ #
8
+ # Parameters::
9
+ # * *cmd* (Hash<Symbol,Object>): The command description:
10
+ # * *dir* (String): Directory in which the command is found
11
+ # * *exe* (String): Name of the executable of the command
12
+ # * *args* (Array<String>): Default arguments of the command [default = []]
13
+ # * *args* (Array<String>): Additional arguments to give the command [default: []]
14
+ def run_cmd(cmd, args: [])
15
+ Dir.chdir cmd[:dir] do
16
+ cmd_line = "\"#{cmd[:exe]}\" #{((cmd.key?(:args) ? cmd[:args] : []) + args).join(' ')}".strip
17
+ raise "Unable to execute command line from \"#{cmd[:dir]}\": #{cmd_line}" unless system cmd_line
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -1,205 +1,204 @@
1
- require 'json'
2
- require 'time'
3
- require 'tmpdir'
4
- require 'modsvaskr/tests_suite'
5
- require 'modsvaskr/in_game_tests_runner'
6
-
7
- module Modsvaskr
8
-
9
- class TestsRunner
10
-
11
- include Logger
12
-
13
- # Constructor.
14
- # Default values are for a standard Skyrim SE installation.
15
- #
16
- # Parameters::
17
- # * *config* (Config): Main configuration
18
- # * *game* (Game): Game for which we run tests
19
- def initialize(config, game)
20
- @config = config
21
- @game = game
22
- # Parse tests suites
23
- @tests_suites = Hash[Dir.glob("#{__dir__}/tests_suites/*.rb").map do |tests_suite_file|
24
- tests_suite = File.basename(tests_suite_file, '.rb').to_sym
25
- require "#{__dir__}/tests_suites/#{tests_suite}.rb"
26
- [
27
- tests_suite,
28
- TestsSuites.const_get(tests_suite.to_s.split('_').collect(&:capitalize).join.to_sym).new(tests_suite, @game)
29
- ]
30
- end]
31
- @tests_info_file = "#{@game.path}/Data/Modsvaskr/Tests/TestsInfo.json"
32
- end
33
-
34
- # Return tests suites
35
- #
36
- # Result::
37
- # * Array<Symbol>: List of tests suites
38
- def tests_suites
39
- @tests_suites.keys.sort
40
- end
41
-
42
- # Return test names for a given tests suite
43
- #
44
- # Parameters::
45
- # * *tests_suite* (Symbol): The tests suite
46
- # Result::
47
- # * Array<String>: Test names of this suite
48
- def discover_tests_for(tests_suite)
49
- log "Discover tests for #{tests_suite}"
50
- discovered_tests = @tests_suites[tests_suite].discover_tests
51
- # Complete our tests information
52
- complete_info = tests_info
53
- discovered_tests.each do |test_name, test_info|
54
- complete_info[tests_suite] = {} unless complete_info.key?(tests_suite)
55
- complete_info[tests_suite][test_name] = test_info
56
- end
57
- update_tests_info(complete_info)
58
- discovered_tests.keys
59
- end
60
-
61
- # Get test statuses for a given tests suite
62
- #
63
- # Parameters::
64
- # * *tests_suite* (Symbol): The tests suite
65
- # Result::
66
- # * Array<[String, String]>: Ordered list of couples [test name, test status]
67
- def statuses_for(tests_suite)
68
- @tests_suites[tests_suite].statuses
69
- end
70
-
71
- # Set test statuses for a given tests suite
72
- #
73
- # Parameters::
74
- # * *tests_suite* (Symbol): The tests suite
75
- # * *statuses* (Array<[String, String]>): Ordered list of couples [test name, test status])
76
- def set_statuses_for(tests_suite, statuses)
77
- @tests_suites[tests_suite].set_statuses(statuses)
78
- end
79
-
80
- # Return test information
81
- #
82
- # Parameters::
83
- # * *tests_suite* (Symbol): The tests suite
84
- # * *test_name* (String): The test name
85
- # Result::
86
- # * Hash<Symbol,Object>: The test information (all properties are optional):
87
- # * *name* (String): The test full name
88
- def test_info(tests_suite, test_name)
89
- tests_info.dig(tests_suite, test_name) || {}
90
- end
91
-
92
- # Clear tests for a given tests suite
93
- #
94
- # Parameters::
95
- # * *tests_suite* (Symbol): The tests suite
96
- def clear_tests_for(tests_suite)
97
- @tests_suites[tests_suite].clear_tests
98
- end
99
-
100
- # Run tests in a loop until they are all tested
101
- #
102
- # Parameters::
103
- # * *selected_tests* (Hash<Symbol, Array<String> >): Ordered list of tests to be run, per tests suite
104
- def run(selected_tests)
105
- # Test names (ordered) to be performed in game, per tests suite
106
- # Hash< Symbol, Array<String> >
107
- in_game_tests = {}
108
- selected_tests.each do |tests_suite, selected_tests|
109
- if @tests_suites[tests_suite].respond_to?(:run_test)
110
- # Simple synchronous tests
111
- selected_tests.each do |test_name|
112
- # Store statuses after each test just in case of crash
113
- set_statuses_for(tests_suite, [[test_name, @tests_suites[tests_suite].run_test(test_name)]])
114
- end
115
- end
116
- # We run the tests from the game itself.
117
- in_game_tests[tests_suite] = selected_tests if @tests_suites[tests_suite].respond_to?(:in_game_tests_for)
118
- end
119
- unless in_game_tests.empty?
120
- # Keep track of the mapping between tests suites and in-game tests, per in-game tests suite.
121
- # Associated info is:
122
- # * *tests_suite* (Symbol): The tests suite that has subscribed to the statuses of some in-game tests of the in-game tests suite.
123
- # * *in_game_tests* (Array<String>): List of in-game tests that the tests suite is interested in.
124
- # * *selected_tests* (Array<String>): List of selected tests for which in-game tests are useful.
125
- # Hash< Symbol, Array< Hash< Symbol, Object > > >
126
- in_game_tests_subscriptions = {}
127
- # List of all in-game tests to perform, per in-game tests suite
128
- # Hash< Symbol, Array< String > >
129
- merged_in_game_tests = {}
130
- # Get the list of in-game tests we have to run and that we will monitor
131
- in_game_tests.each do |tests_suite, selected_tests|
132
- in_game_tests_to_subscribe = @tests_suites[tests_suite].in_game_tests_for(selected_tests)
133
- in_game_tests_to_subscribe.each do |in_game_tests_suite, selected_in_game_tests|
134
- selected_in_game_tests_downcase = selected_in_game_tests.map(&:downcase)
135
- in_game_tests_subscriptions[in_game_tests_suite] = [] unless in_game_tests_subscriptions.key?(in_game_tests_suite)
136
- in_game_tests_subscriptions[in_game_tests_suite] << {
137
- tests_suite: tests_suite,
138
- in_game_tests: selected_in_game_tests_downcase,
139
- selected_tests: selected_tests
140
- }
141
- merged_in_game_tests[in_game_tests_suite] = [] unless merged_in_game_tests.key?(in_game_tests_suite)
142
- merged_in_game_tests[in_game_tests_suite] = (merged_in_game_tests[in_game_tests_suite] + selected_in_game_tests_downcase).uniq
143
- end
144
- end
145
- in_game_tests_runner = InGameTestsRunner.new(@config, @game)
146
- in_game_tests_runner.run(merged_in_game_tests) do |in_game_tests_suite, in_game_tests_statuses|
147
- # This is a callback called for each in-game test status change.
148
- # Update the tests results based on what has been run in-game.
149
- # Find all tests suites that are subscribed to those in-game tests.
150
- # Be careful that updates can be given for in-game tests suites we were not expecting
151
- if in_game_tests_subscriptions.key?(in_game_tests_suite)
152
- in_game_tests_subscriptions[in_game_tests_suite].each do |tests_suite_subscription|
153
- selected_in_game_tests_statuses = in_game_tests_statuses.slice(*tests_suite_subscription[:in_game_tests])
154
- unless selected_in_game_tests_statuses.empty?
155
- tests_suite = @tests_suites[tests_suite_subscription[:tests_suite]]
156
- tests_suite.set_statuses(
157
- tests_suite.
158
- parse_auto_tests_statuses_for(tests_suite_subscription[:selected_tests], { in_game_tests_suite => selected_in_game_tests_statuses }).
159
- select { |(test_name, _test_status)| tests_suite_subscription[:selected_tests].include?(test_name) }
160
- )
161
- end
162
- end
163
- end
164
- end
165
- end
166
- end
167
-
168
- private
169
-
170
- # Return all tests info.
171
- # Keep a cache of it.
172
- #
173
- # Result::
174
- # * Hash< Symbol, Hash< String, Hash<Symbol,Object> > >: The tests info, per test name, per tests suite
175
- def tests_info
176
- unless defined?(@tests_info_cache)
177
- @tests_info_cache =
178
- if File.exist?(@tests_info_file)
179
- Hash[JSON.parse(File.read(@tests_info_file)).map do |tests_suite_str, tests_suite_info|
180
- [
181
- tests_suite_str.to_sym,
182
- Hash[tests_suite_info.map { |test_name, test_info| [test_name, test_info.transform_keys(&:to_sym)] }]
183
- ]
184
- end]
185
- else
186
- {}
187
- end
188
- end
189
- @tests_info_cache
190
- end
191
-
192
- # Update tests info.
193
- #
194
- # Parameters::
195
- # * *tests_info* (Hash< Symbol, Hash< String, Hash<Symbol,Object> > >): The tests info, per test name, per tests suite
196
- def update_tests_info(tests_info)
197
- # Persist the tests information on disk
198
- FileUtils.mkdir_p File.dirname(@tests_info_file)
199
- File.write(@tests_info_file, JSON.pretty_generate(tests_info))
200
- @tests_info_cache = tests_info
201
- end
202
-
203
- end
204
-
205
- end
1
+ require 'json'
2
+ require 'time'
3
+ require 'tmpdir'
4
+ require 'modsvaskr/tests_suite'
5
+ require 'modsvaskr/in_game_tests_runner'
6
+
7
+ module Modsvaskr
8
+
9
+ # Execute a bunch of tests on games
10
+ class TestsRunner
11
+
12
+ include Logger
13
+
14
+ # Constructor.
15
+ # Default values are for a standard Skyrim SE installation.
16
+ #
17
+ # Parameters::
18
+ # * *config* (Config): Main configuration
19
+ # * *game* (Game): Game for which we run tests
20
+ def initialize(config, game)
21
+ @config = config
22
+ @game = game
23
+ # Parse tests suites
24
+ @tests_suites = Dir.glob("#{__dir__}/tests_suites/*.rb").map do |tests_suite_file|
25
+ tests_suite = File.basename(tests_suite_file, '.rb').to_sym
26
+ require "#{__dir__}/tests_suites/#{tests_suite}.rb"
27
+ [
28
+ tests_suite,
29
+ TestsSuites.const_get(tests_suite.to_s.split('_').collect(&:capitalize).join.to_sym).new(tests_suite, @game)
30
+ ]
31
+ end.to_h
32
+ @tests_info_file = "#{@game.path}/Data/Modsvaskr/Tests/TestsInfo.json"
33
+ end
34
+
35
+ # Return tests suites
36
+ #
37
+ # Result::
38
+ # * Array<Symbol>: List of tests suites
39
+ def tests_suites
40
+ @tests_suites.keys.sort
41
+ end
42
+
43
+ # Return test names for a given tests suite
44
+ #
45
+ # Parameters::
46
+ # * *tests_suite* (Symbol): The tests suite
47
+ # Result::
48
+ # * Array<String>: Test names of this suite
49
+ def discover_tests_for(tests_suite)
50
+ log "Discover tests for #{tests_suite}"
51
+ discovered_tests = @tests_suites[tests_suite].discover_tests
52
+ # Complete our tests information
53
+ complete_info = tests_info
54
+ discovered_tests.each do |test_name, test_info|
55
+ complete_info[tests_suite] = {} unless complete_info.key?(tests_suite)
56
+ complete_info[tests_suite][test_name] = test_info
57
+ end
58
+ update_tests_info(complete_info)
59
+ discovered_tests.keys
60
+ end
61
+
62
+ # Get test statuses for a given tests suite
63
+ #
64
+ # Parameters::
65
+ # * *tests_suite* (Symbol): The tests suite
66
+ # Result::
67
+ # * Array<[String, String]>: Ordered list of couples [test name, test status]
68
+ def statuses_for(tests_suite)
69
+ @tests_suites[tests_suite].statuses
70
+ end
71
+
72
+ # Set test statuses for a given tests suite
73
+ #
74
+ # Parameters::
75
+ # * *tests_suite* (Symbol): The tests suite
76
+ # * *statuses* (Array<[String, String]>): Ordered list of couples [test name, test status])
77
+ def set_statuses_for(tests_suite, statuses)
78
+ @tests_suites[tests_suite].statuses = statuses
79
+ end
80
+
81
+ # Return test information
82
+ #
83
+ # Parameters::
84
+ # * *tests_suite* (Symbol): The tests suite
85
+ # * *test_name* (String): The test name
86
+ # Result::
87
+ # * Hash<Symbol,Object>: The test information (all properties are optional):
88
+ # * *name* (String): The test full name
89
+ def test_info(tests_suite, test_name)
90
+ tests_info.dig(tests_suite, test_name) || {}
91
+ end
92
+
93
+ # Clear tests for a given tests suite
94
+ #
95
+ # Parameters::
96
+ # * *tests_suite* (Symbol): The tests suite
97
+ def clear_tests_for(tests_suite)
98
+ @tests_suites[tests_suite].clear_tests
99
+ end
100
+
101
+ # Run tests in a loop until they are all tested
102
+ #
103
+ # Parameters::
104
+ # * *selected_tests* (Hash<Symbol, Array<String> >): Ordered list of tests to be run, per tests suite
105
+ def run(selected_tests)
106
+ # Test names (ordered) to be performed in game, per tests suite
107
+ # Hash< Symbol, Array<String> >
108
+ in_game_tests = {}
109
+ selected_tests.each do |tests_suite, suite_selected_tests|
110
+ if @tests_suites[tests_suite].respond_to?(:run_test)
111
+ # Simple synchronous tests
112
+ suite_selected_tests.each do |test_name|
113
+ # Store statuses after each test just in case of crash
114
+ set_statuses_for(tests_suite, [[test_name, @tests_suites[tests_suite].run_test(test_name)]])
115
+ end
116
+ end
117
+ # We run the tests from the game itself.
118
+ in_game_tests[tests_suite] = suite_selected_tests if @tests_suites[tests_suite].respond_to?(:in_game_tests_for)
119
+ end
120
+ return if in_game_tests.empty?
121
+
122
+ # Keep track of the mapping between tests suites and in-game tests, per in-game tests suite.
123
+ # Associated info is:
124
+ # * *tests_suite* (Symbol): The tests suite that has subscribed to the statuses of some in-game tests of the in-game tests suite.
125
+ # * *in_game_tests* (Array<String>): List of in-game tests that the tests suite is interested in.
126
+ # * *selected_tests* (Array<String>): List of selected tests for which in-game tests are useful.
127
+ # Hash< Symbol, Array< Hash< Symbol, Object > > >
128
+ in_game_tests_subscriptions = {}
129
+ # List of all in-game tests to perform, per in-game tests suite
130
+ # Hash< Symbol, Array< String > >
131
+ merged_in_game_tests = {}
132
+ # Get the list of in-game tests we have to run and that we will monitor
133
+ in_game_tests.each do |tests_suite, suite_selected_tests|
134
+ in_game_tests_to_subscribe = @tests_suites[tests_suite].in_game_tests_for(suite_selected_tests)
135
+ in_game_tests_to_subscribe.each do |in_game_tests_suite, selected_in_game_tests|
136
+ selected_in_game_tests_downcase = selected_in_game_tests.map(&:downcase)
137
+ in_game_tests_subscriptions[in_game_tests_suite] = [] unless in_game_tests_subscriptions.key?(in_game_tests_suite)
138
+ in_game_tests_subscriptions[in_game_tests_suite] << {
139
+ tests_suite: tests_suite,
140
+ in_game_tests: selected_in_game_tests_downcase,
141
+ selected_tests: suite_selected_tests
142
+ }
143
+ merged_in_game_tests[in_game_tests_suite] = [] unless merged_in_game_tests.key?(in_game_tests_suite)
144
+ merged_in_game_tests[in_game_tests_suite] = (merged_in_game_tests[in_game_tests_suite] + selected_in_game_tests_downcase).uniq
145
+ end
146
+ end
147
+ in_game_tests_runner = InGameTestsRunner.new(@config, @game)
148
+ in_game_tests_runner.run(merged_in_game_tests) do |in_game_tests_suite, in_game_tests_statuses|
149
+ # This is a callback called for each in-game test status change.
150
+ # Update the tests results based on what has been run in-game.
151
+ # Find all tests suites that are subscribed to those in-game tests.
152
+ # Be careful that updates can be given for in-game tests suites we were not expecting
153
+ if in_game_tests_subscriptions.key?(in_game_tests_suite)
154
+ in_game_tests_subscriptions[in_game_tests_suite].each do |tests_suite_subscription|
155
+ selected_in_game_tests_statuses = in_game_tests_statuses.slice(*tests_suite_subscription[:in_game_tests])
156
+ next if selected_in_game_tests_statuses.empty?
157
+
158
+ tests_suite = @tests_suites[tests_suite_subscription[:tests_suite]]
159
+ tests_suite.statuses = tests_suite.
160
+ parse_auto_tests_statuses_for(tests_suite_subscription[:selected_tests], { in_game_tests_suite => selected_in_game_tests_statuses }).
161
+ select { |(test_name, _test_status)| tests_suite_subscription[:selected_tests].include?(test_name) }
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+ private
168
+
169
+ # Return all tests info.
170
+ # Keep a cache of it.
171
+ #
172
+ # Result::
173
+ # * Hash< Symbol, Hash< String, Hash<Symbol,Object> > >: The tests info, per test name, per tests suite
174
+ def tests_info
175
+ unless defined?(@tests_info_cache)
176
+ @tests_info_cache =
177
+ if File.exist?(@tests_info_file)
178
+ JSON.parse(File.read(@tests_info_file)).map do |tests_suite_str, tests_suite_info|
179
+ [
180
+ tests_suite_str.to_sym,
181
+ tests_suite_info.transform_values { |test_info| test_info.transform_keys(&:to_sym) }
182
+ ]
183
+ end.to_h
184
+ else
185
+ {}
186
+ end
187
+ end
188
+ @tests_info_cache
189
+ end
190
+
191
+ # Update tests info.
192
+ #
193
+ # Parameters::
194
+ # * *tests_info* (Hash< Symbol, Hash< String, Hash<Symbol,Object> > >): The tests info, per test name, per tests suite
195
+ def update_tests_info(tests_info)
196
+ # Persist the tests information on disk
197
+ FileUtils.mkdir_p File.dirname(@tests_info_file)
198
+ File.write(@tests_info_file, JSON.pretty_generate(tests_info))
199
+ @tests_info_cache = tests_info
200
+ end
201
+
202
+ end
203
+
204
+ end