modsvaskr 0.1.11 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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").to_h 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
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:,
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)).to_h 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
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