spinel_test-unit 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f445b0e6c7e31a0821e0ac04121407864475e56e85ae48326e548ded202b94d2
4
- data.tar.gz: f541193fc3cda7755d9a4d233367846af00822a84daf209d8015c1f5b5326304
3
+ metadata.gz: ef62e948140f04c676b0709a5780239556c38dd521fbdd0fce4968f82b643fe4
4
+ data.tar.gz: 3212cc091c366bc33b3fc1854f3fbd6a4a164248baa20cdc3816d3896dcd8519
5
5
  SHA512:
6
- metadata.gz: 1d3e3ec35074600ed21fb5765bc60b72e77235567850c5ecff1811f9ceb9d7b3fd8db73be99817f39dde0864e5a20b6962c5a6f943c4fabe4f4475a5525290a8
7
- data.tar.gz: 892971495ea188d9e80c4f962cb6121cfab754d4db98c7a2c8f8e8809d161166522586d0b15a0bee6837068dbc0daa0ece5ff2ecb79ccdaa0d9412bd14f64d0e
6
+ metadata.gz: 9dcc12b871aa16d10cea18c038de5d335a4e07121098f3de6b3449be0020e82274d39593698b79a39457dd9049ccdb1fcc06c740458d536151dd592ba93b66e9
7
+ data.tar.gz: 588963b23dc23425e40721efad1070800e577aec65d9dbba959aa537f3055ddb0a06db18df1671ae484733d5d7055f2f0da8e102213cbc2598897a618be0e43b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## Unreleased
2
2
 
3
+ ## 0.3.0
4
+
5
+ * Add `match:` to `assert_raise` to check the exception message, as in `assert_match` (a Regexp, or a String meaning a substring)
6
+ * `assert_raise` passes only for the given class itself, not a subclass, as in test-unit. Use the new `assert_raise_kind_of` to also accept a subclass
7
+ * Add `-t`/`--testcase CLASS` to run only the tests of the class named exactly CLASS (`-n FooTest#` also selects `MyFooTest`)
8
+ * `-n` and `-t` take a regexp written as `/regexp/`, as in test-unit
9
+
3
10
  ## 0.2.0
4
11
 
5
12
  * Report where a test failed: `at file:line` lines after the message (the test method's line with Spinel, the filtered backtrace with `--ruby`)
data/README.md CHANGED
@@ -39,6 +39,9 @@ end
39
39
  $ bundle exec spinel_test-unit # test/**/*_test.rb and test/**/test_*.rb
40
40
  $ bundle exec spinel_test-unit test/calc_test.rb
41
41
  $ bundle exec spinel_test-unit -n test_add # only tests whose "Class#method" name includes it
42
+ $ bundle exec spinel_test-unit -n '/#test_add\z/' # or matches the regexp
43
+ $ bundle exec spinel_test-unit -t CalcTest # only the tests of the class named exactly CalcTest
44
+ $ bundle exec spinel_test-unit -t '/\ACalc/' # or of the classes whose name matches the regexp
42
45
  $ bundle exec spinel_test-unit --ruby # run the same tests with the current Ruby, no Spinel needed
43
46
  ```
44
47
 
@@ -93,7 +96,8 @@ Available assertions:
93
96
  - `assert_include(collection, item, message = nil)` (`assert_includes` is an alias)
94
97
  - `assert_not_include(collection, item, message = nil)`
95
98
  - `refute(test, message = nil)`
96
- - `assert_raise(klass) { ... }` (passes for `klass` or a subclass; returns the exception; assertion failures raised inside the block propagate)
99
+ - `assert_raise(klass, match: nil) { ... }` (passes for `klass` itself, not a subclass, as in test-unit; returns the exception; assertion failures raised inside the block propagate). With `match:`, the exception's message must also match it, as in `assert_match` (a Regexp, or a String meaning a substring)
100
+ - `assert_raise_kind_of(klass, match: nil) { ... }` (as `assert_raise`, but also passes for a subclass of `klass`)
97
101
 
98
102
  `setup` and `teardown` hooks are supported.
99
103
 
@@ -10,7 +10,7 @@ require_relative "toolchain"
10
10
 
11
11
  module SpinelTest
12
12
  module Unit
13
- # spinel_test-unit [PATH...] [-n NAME] [-I DIR] [--ruby]
13
+ # spinel_test-unit [PATH...] [-n NAME] [-t CLASS] [-I DIR] [--ruby]
14
14
  #
15
15
  # Scans the test files with Prism, generates a main.rb that calls every
16
16
  # test, compiles it with Spinel and runs the binary. With --ruby it runs the
@@ -25,7 +25,7 @@ module SpinelTest
25
25
  @root = File.expand_path(root)
26
26
  @paths = []
27
27
  @filter = nil
28
- @keep = false
28
+ @testcase = nil
29
29
  @ruby = false
30
30
  @includes = []
31
31
  @exit = nil
@@ -33,13 +33,17 @@ module SpinelTest
33
33
  end
34
34
 
35
35
  USAGE = <<~TEXT
36
- Usage: spinel_test-unit [PATH...] [-n NAME] [-I DIR] [--ruby]
36
+ Usage: spinel_test-unit [PATH...] [-n NAME] [-t CLASS] [-I DIR] [--ruby]
37
37
 
38
38
  Compiles the tests with Spinel and runs them (with --ruby, runs them with the
39
39
  current Ruby instead).
40
40
 
41
41
  PATH test file or directory (default: test)
42
- -n, --name NAME run only the tests whose "Class#test_name" includes NAME
42
+ -n, --name NAME run only the tests whose "Class#test_name" includes NAME,
43
+ or matches it when NAME is /regexp/
44
+ -t, --testcase CLASS
45
+ run only the tests of the class named CLASS (exact match),
46
+ or whose class name matches it when CLASS is /regexp/
43
47
  -I DIR add DIR to the load path (repeatable)
44
48
  --ruby run with the current Ruby instead of compiling with Spinel
45
49
  -h, --help show this help
@@ -93,7 +97,9 @@ module SpinelTest
93
97
  end
94
98
 
95
99
  def run_command(command)
96
- args = @filter ? ["-n", @filter] : []
100
+ args = []
101
+ args += ["-n", @filter] if @filter
102
+ args += ["-t", @testcase] if @testcase
97
103
  system(*command, *args) ? 0 : ($?.exitstatus || 1)
98
104
  end
99
105
 
@@ -106,17 +112,27 @@ module SpinelTest
106
112
  @exit = 0
107
113
  return
108
114
  when "-n", "--name"
109
- if args.empty?
110
- return usage_error("#{arg} requires a value")
111
- end
115
+ return usage_error("#{arg} requires a value") if args.empty?
116
+
112
117
  @filter = args.shift
118
+ return unless valid_pattern?(@filter)
119
+ when "-t", "--testcase"
120
+ return usage_error("#{arg} requires a value") if args.empty?
121
+
122
+ @testcase = args.shift
123
+ return unless valid_pattern?(@testcase)
113
124
  when "--ruby" then @ruby = true
114
125
  when "-I"
115
126
  return usage_error("-I requires a value") if args.empty?
116
127
 
117
128
  @includes << File.expand_path(args.shift, @root)
118
129
  when /\A-I(.+)\z/ then @includes << File.expand_path($1, @root)
119
- when /\A--name=(.*)\z/ then @filter = $1
130
+ when /\A--name=(.*)\z/
131
+ @filter = $1
132
+ return unless valid_pattern?(@filter)
133
+ when /\A--testcase=(.*)\z/
134
+ @testcase = $1
135
+ return unless valid_pattern?(@testcase)
120
136
  when "--" then @paths.concat(args.shift(args.size))
121
137
  when /\A-/ then return usage_error("unknown option: #{arg}")
122
138
  else @paths << arg
@@ -124,6 +140,16 @@ module SpinelTest
124
140
  end
125
141
  end
126
142
 
143
+ def valid_pattern?(value)
144
+ return true unless value.length >= 2 && value.start_with?("/") && value.end_with?("/")
145
+
146
+ Regexp.new(value[1..-2])
147
+ true
148
+ rescue RegexpError => e
149
+ usage_error("invalid regexp #{value}: #{e.message}")
150
+ false
151
+ end
152
+
127
153
  def usage_error(message)
128
154
  warn "spinel_test-unit: #{message}"
129
155
  warn USAGE
@@ -245,7 +271,13 @@ module SpinelTest
245
271
 
246
272
  def compile(spinel, main, bin, entries)
247
273
  cmd = [spinel] + Toolchain.compile_flags + include_dirs(entries).flat_map { |d| ["-I", d] } + [main, "-o", bin]
248
- output, status = Open3.capture2e(*cmd)
274
+ begin
275
+ output, status = Open3.capture2e(*cmd)
276
+ rescue SystemCallError => e
277
+ hint = ENV["SPINEL"].to_s.empty? ? "" : " (check SPINEL)"
278
+ warn "spinel_test-unit: cannot run Spinel: #{e.message}#{hint}"
279
+ return false
280
+ end
249
281
  unless status.success?
250
282
  warn output unless output.empty?
251
283
  warn "spinel_test-unit: compilation failed (#{cmd.join(" ")})"
@@ -59,10 +59,14 @@ module SpinelTest
59
59
  end
60
60
 
61
61
  def ambiguous?(line)
62
- line == "" || line == "..." || line.include?("\r") || line.start_with?("\"") ||
62
+ line == "" || line == "..." || control?(line) || line.start_with?("\"") ||
63
63
  line.start_with?(" ", "\t") || line.end_with?(" ", "\t")
64
64
  end
65
65
 
66
+ def control?(line)
67
+ line.bytes.any? { |b| (b < 32 && b != 9) || b == 127 }
68
+ end
69
+
66
70
  def edits
67
71
  lengths = lcs_lengths
68
72
  entries = []
@@ -9,7 +9,7 @@ module SpinelTest
9
9
  out = +"require \"spinel_test/unit\"\n"
10
10
  files.each { |f| out << "require #{f[:require].dump}\n" }
11
11
  out << ARGV_PARSER
12
- out << "runner = SpinelTest::Unit::Runner.new(filter, #{root.dump})\n"
12
+ out << "runner = SpinelTest::Unit::Runner.new(filter, #{root.dump}, testcase)\n"
13
13
  files.each do |f|
14
14
  f[:classes].each do |klass|
15
15
  locations = klass.locations || {}
@@ -25,11 +25,15 @@ module SpinelTest
25
25
 
26
26
  ARGV_PARSER = <<~RUBY
27
27
  filter = ""
28
+ testcase = ""
28
29
  i = 0
29
30
  while i < ARGV.length
30
31
  if ARGV[i] == "-n" && i + 1 < ARGV.length
31
32
  filter = ARGV[i + 1]
32
33
  i += 1
34
+ elsif ARGV[i] == "-t" && i + 1 < ARGV.length
35
+ testcase = ARGV[i + 1]
36
+ i += 1
33
37
  end
34
38
  i += 1
35
39
  end
@@ -6,9 +6,10 @@ module SpinelTest
6
6
  MAX_FRAMES = 3
7
7
  FRAMEWORK_DIR = File.dirname(File.expand_path(__FILE__)) + "/"
8
8
 
9
- def initialize(filter = "", root = "")
9
+ def initialize(filter = "", root = "", testcase = "")
10
10
  @filter = filter
11
11
  @root = root
12
+ @testcase = testcase
12
13
  @tests = 0
13
14
  @assertions = 0
14
15
  @failures = 0
@@ -17,7 +18,20 @@ module SpinelTest
17
18
  end
18
19
 
19
20
  def selected?(name)
20
- @filter == "" || name.include?(@filter)
21
+ index = name.index("#")
22
+ klass = index ? name[0, index] : name
23
+ matches?(@testcase, klass, true) && matches?(@filter, name, false)
24
+ end
25
+
26
+ def matches?(filter, text, exact)
27
+ return true if filter == ""
28
+ return Regexp.new(filter[1, filter.length - 2]).match?(text) if pattern?(filter)
29
+
30
+ exact ? text == filter : text.include?(filter)
31
+ end
32
+
33
+ def pattern?(filter)
34
+ filter.length >= 2 && filter.start_with?("/") && filter.end_with?("/")
21
35
  end
22
36
 
23
37
  # Runs one test: setup, the block (which calls the test method), teardown.
@@ -120,6 +134,13 @@ module SpinelTest
120
134
  e.is_a?(SystemExit) || e.is_a?(Interrupt)
121
135
  end
122
136
 
137
+ def filter_description
138
+ parts = []
139
+ parts.push("testcase: " + @testcase) if @testcase != ""
140
+ parts.push("filter: " + @filter) if @filter != ""
141
+ parts.empty? ? "" : " (" + parts.join(", ") + ")"
142
+ end
143
+
123
144
  def passed?
124
145
  @failures == 0 && @errors == 0
125
146
  end
@@ -138,7 +159,7 @@ module SpinelTest
138
159
  puts ""
139
160
  puts summary
140
161
  if @tests == 0
141
- puts "No tests were run#{@filter == "" ? "" : " (filter: " + @filter + ")"}"
162
+ puts "No tests were run#{filter_description}"
142
163
  return 1
143
164
  end
144
165
  passed? ? 0 : 1
@@ -38,55 +38,42 @@ module SpinelTest
38
38
  @assertions += 1
39
39
  return true if expected == actual
40
40
 
41
- msg = "Expected: #{expected.inspect}\n Actual: #{actual.inspect}"
42
- msg += diff_section(expected, actual)
43
- msg = message + "\n" + msg if message
44
- raise AssertionFailedError, msg
41
+ failure("Expected: #{expected.inspect}\n Actual: #{actual.inspect}" + diff_section(expected, actual), message)
45
42
  end
46
43
 
47
44
  def assert_not_equal(expected, actual, message = nil)
48
45
  @assertions += 1
49
46
  return true unless expected == actual
50
47
 
51
- msg = "Expected #{actual.inspect} not to equal #{expected.inspect}"
52
- msg = message + "\n" + msg if message
53
- raise AssertionFailedError, msg
48
+ failure("Expected #{actual.inspect} not to equal #{expected.inspect}", message)
54
49
  end
55
50
 
56
51
  def assert_match(pattern, string, message = nil)
57
52
  @assertions += 1
58
53
  return true if pattern_matches?(pattern, string)
59
54
 
60
- msg = "Expected #{pattern.inspect} to match #{string.inspect}"
61
- msg = message + "\n" + msg if message
62
- raise AssertionFailedError, msg
55
+ failure("Expected #{pattern.inspect} to match #{string.inspect}", message)
63
56
  end
64
57
 
65
58
  def assert_nil(value, message = nil)
66
59
  @assertions += 1
67
60
  return true if value.nil?
68
61
 
69
- msg = "Expected nil, got #{value.inspect}"
70
- msg = message + "\n" + msg if message
71
- raise AssertionFailedError, msg
62
+ failure("Expected nil, got #{value.inspect}", message)
72
63
  end
73
64
 
74
65
  def assert_not_nil(value, message = nil)
75
66
  @assertions += 1
76
67
  return true unless value.nil?
77
68
 
78
- msg = "Expected a value other than nil"
79
- msg = message + "\n" + msg if message
80
- raise AssertionFailedError, msg
69
+ failure("Expected a value other than nil", message)
81
70
  end
82
71
 
83
72
  def assert_include(collection, item, message = nil)
84
73
  @assertions += 1
85
74
  return true if collection.include?(item)
86
75
 
87
- msg = "#{collection.inspect} does not include #{item.inspect}"
88
- msg = message + "\n" + msg if message
89
- raise AssertionFailedError, msg
76
+ failure("#{collection.inspect} does not include #{item.inspect}", message)
90
77
  end
91
78
 
92
79
  def assert_includes(collection, item, message = nil)
@@ -97,12 +84,10 @@ module SpinelTest
97
84
  @assertions += 1
98
85
  return true unless collection.include?(item)
99
86
 
100
- msg = "#{collection.inspect} includes #{item.inspect}"
101
- msg = message + "\n" + msg if message
102
- raise AssertionFailedError, msg
87
+ failure("#{collection.inspect} includes #{item.inspect}", message)
103
88
  end
104
89
 
105
- def assert_raise(klass)
90
+ def assert_raise(klass, match: nil)
106
91
  @assertions += 1
107
92
  begin
108
93
  yield
@@ -110,15 +95,36 @@ module SpinelTest
110
95
  # a failed assertion inside the block is not "the expected error"
111
96
  raise e
112
97
  rescue StandardError => e
113
- return e if e.is_a?(klass)
98
+ return expected_exception(e, klass, match, e.instance_of?(klass))
99
+ end
100
+ raise AssertionFailedError, "Expected #{klass} but nothing was raised"
101
+ end
114
102
 
115
- raise AssertionFailedError, "Expected #{klass} but got #{e.class}"
103
+ def assert_raise_kind_of(klass, match: nil)
104
+ @assertions += 1
105
+ begin
106
+ yield
107
+ rescue AssertionFailedError => e
108
+ raise e
109
+ rescue StandardError => e
110
+ return expected_exception(e, klass, match, e.is_a?(klass))
116
111
  end
117
112
  raise AssertionFailedError, "Expected #{klass} but nothing was raised"
118
113
  end
119
114
 
120
115
  private
121
116
 
117
+ def failure(text, message)
118
+ raise AssertionFailedError, message ? message + "\n" + text : text
119
+ end
120
+
121
+ def expected_exception(e, klass, match, class_matches)
122
+ raise AssertionFailedError, "Expected #{klass} but got #{e.class}" unless class_matches
123
+ return e if match.nil? || pattern_matches?(match, e.message)
124
+
125
+ raise AssertionFailedError, "Expected #{klass} with a message matching #{match.inspect} but got #{e.message.inspect}"
126
+ end
127
+
122
128
  def diff_section(expected, actual)
123
129
  return "" unless multiline_strings?(expected, actual)
124
130
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module SpinelTest
4
4
  module Unit
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spinel_test-unit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuji Yaginuma
@@ -71,6 +71,7 @@ licenses:
71
71
  metadata:
72
72
  homepage_uri: https://github.com/y-yagi/spinel_test-unit
73
73
  source_code_uri: https://github.com/y-yagi/spinel_test-unit
74
+ rubygems_mfa_required: 'true'
74
75
  rdoc_options: []
75
76
  require_paths:
76
77
  - lib