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.
- checksums.yaml +4 -4
- data/bin/modsvaskr +11 -11
- data/lib/modsvaskr/config.rb +78 -79
- data/lib/modsvaskr/encoding.rb +31 -0
- data/lib/modsvaskr/game.rb +208 -179
- data/lib/modsvaskr/games/skyrim_se.rb +106 -92
- data/lib/modsvaskr/in_game_tests_runner.rb +348 -348
- data/lib/modsvaskr/logger.rb +45 -44
- data/lib/modsvaskr/run_cmd.rb +23 -22
- data/lib/modsvaskr/tests_runner.rb +204 -205
- data/lib/modsvaskr/tests_suite.rb +70 -69
- data/lib/modsvaskr/tests_suites/exterior_cell.rb +120 -117
- data/lib/modsvaskr/tests_suites/interior_cell.rb +63 -63
- data/lib/modsvaskr/tests_suites/npc.rb +67 -39
- data/lib/modsvaskr/tests_suites/npc_head.rb +6 -10
- data/lib/modsvaskr/ui.rb +205 -205
- data/lib/modsvaskr/version.rb +5 -5
- data/lib/modsvaskr/xedit.rb +65 -52
- data/xedit_scripts/Modsvaskr_DumpInfo.pas +13 -0
- metadata +39 -10
data/lib/modsvaskr/logger.rb
CHANGED
@@ -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
|
-
|
8
|
-
attr_accessor :stdout_io
|
9
|
-
|
10
|
-
|
11
|
-
@
|
12
|
-
|
13
|
-
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
data/lib/modsvaskr/run_cmd.rb
CHANGED
@@ -1,22 +1,23 @@
|
|
1
|
-
module Modsvaskr
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# * *
|
11
|
-
# * *
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
# * *
|
19
|
-
|
20
|
-
|
21
|
-
@
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
complete_info[tests_suite]
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
# * *
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
# * *
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
|
105
|
-
|
106
|
-
#
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
#
|
171
|
-
#
|
172
|
-
#
|
173
|
-
#
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
#
|
193
|
-
#
|
194
|
-
#
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
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
|