smartest 0.1.0.alpha1
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/CHANGELOG.md +16 -0
- data/DEVELOPMENT.md +774 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +518 -0
- data/Rakefile +12 -0
- data/SMARTEST_DESIGN.md +1137 -0
- data/exe/smartest +63 -0
- data/lib/smartest/autorun.rb +8 -0
- data/lib/smartest/dsl.rb +22 -0
- data/lib/smartest/errors.rb +52 -0
- data/lib/smartest/execution_context.rb +8 -0
- data/lib/smartest/expectation_target.rb +21 -0
- data/lib/smartest/expectations.rb +9 -0
- data/lib/smartest/fixture.rb +78 -0
- data/lib/smartest/fixture_class_registry.rb +27 -0
- data/lib/smartest/fixture_definition.rb +31 -0
- data/lib/smartest/fixture_set.rb +119 -0
- data/lib/smartest/init_generator.rb +70 -0
- data/lib/smartest/matchers.rb +109 -0
- data/lib/smartest/parameter_extractor.rb +51 -0
- data/lib/smartest/reporter.rb +91 -0
- data/lib/smartest/runner.rb +80 -0
- data/lib/smartest/suite.rb +12 -0
- data/lib/smartest/test_case.rb +18 -0
- data/lib/smartest/test_registry.rb +25 -0
- data/lib/smartest/test_result.rb +43 -0
- data/lib/smartest/version.rb +5 -0
- data/lib/smartest.rb +59 -0
- data/smartest/smartest_test.rb +634 -0
- data/smartest.gemspec +48 -0
- metadata +95 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
4
|
+
|
|
5
|
+
require "smartest/autorun"
|
|
6
|
+
require "fileutils"
|
|
7
|
+
require "stringio"
|
|
8
|
+
require "open3"
|
|
9
|
+
require "tmpdir"
|
|
10
|
+
|
|
11
|
+
module SmartestSelfTest
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def test_case(name, block)
|
|
15
|
+
Smartest::TestCase.new(
|
|
16
|
+
name: name,
|
|
17
|
+
metadata: {},
|
|
18
|
+
block: block,
|
|
19
|
+
location: caller_locations(1, 1).first
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def run_suite(suite)
|
|
24
|
+
output = StringIO.new
|
|
25
|
+
status = Smartest::Runner.new(suite: suite, reporter: Smartest::Reporter.new(output)).run
|
|
26
|
+
|
|
27
|
+
[status, output.string]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def capture_error(expected_error)
|
|
31
|
+
yield
|
|
32
|
+
rescue Exception => error
|
|
33
|
+
raise if Smartest.fatal_exception?(error)
|
|
34
|
+
|
|
35
|
+
unless error.is_a?(expected_error)
|
|
36
|
+
raise Smartest::AssertionFailed, "expected #{expected_error}, but raised #{error.class}: #{error.message}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
error
|
|
40
|
+
else
|
|
41
|
+
raise Smartest::AssertionFailed, "expected #{expected_error}, but nothing was raised"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class SelfTestRegisteredFixture < Smartest::Fixture
|
|
46
|
+
fixture :registered_user_name do
|
|
47
|
+
"Alice"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
use_fixture SelfTestRegisteredFixture
|
|
52
|
+
|
|
53
|
+
test("registers fixture classes with use_fixture") do |registered_user_name:|
|
|
54
|
+
expect(registered_user_name).to eq("Alice")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
test("runs a registered test") do
|
|
58
|
+
suite = Smartest::Suite.new
|
|
59
|
+
suite.tests.add(SmartestSelfTest.test_case("factorial", proc { expect(1 * 2 * 3).to eq(6) }))
|
|
60
|
+
|
|
61
|
+
status, output = SmartestSelfTest.run_suite(suite)
|
|
62
|
+
|
|
63
|
+
expect(status).to eq(0)
|
|
64
|
+
expect(output).to include("Running 1 test")
|
|
65
|
+
expect(output).to include("1 test, 1 passed, 0 failed")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
test("reports expectation failures") do
|
|
69
|
+
suite = Smartest::Suite.new
|
|
70
|
+
suite.tests.add(SmartestSelfTest.test_case("bad math", proc { expect(1 + 1).to eq(3) }))
|
|
71
|
+
|
|
72
|
+
status, output = SmartestSelfTest.run_suite(suite)
|
|
73
|
+
|
|
74
|
+
expect(status).to eq(1)
|
|
75
|
+
expect(output).to include("expected 2 to eq 3")
|
|
76
|
+
expect(output).to include("1 test, 0 passed, 1 failed")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
test("supports basic matchers") do
|
|
80
|
+
suite = Smartest::Suite.new
|
|
81
|
+
suite.tests.add(
|
|
82
|
+
SmartestSelfTest.test_case(
|
|
83
|
+
"matchers",
|
|
84
|
+
proc do
|
|
85
|
+
expect([1, 2, 3]).to include(2)
|
|
86
|
+
expect(nil).to be_nil
|
|
87
|
+
expect("value").not_to be_nil
|
|
88
|
+
expect { raise ArgumentError, "bad" }.to raise_error(ArgumentError)
|
|
89
|
+
end
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
status, = SmartestSelfTest.run_suite(suite)
|
|
94
|
+
|
|
95
|
+
expect(status).to eq(0)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
test("resolves keyword fixture dependencies per test") do
|
|
99
|
+
calls = []
|
|
100
|
+
|
|
101
|
+
fixture_class = Class.new(Smartest::Fixture) do
|
|
102
|
+
fixture :user do
|
|
103
|
+
calls << :user
|
|
104
|
+
"Alice"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
fixture :greeting do |user:|
|
|
108
|
+
"Hello, #{user}"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
suite = Smartest::Suite.new
|
|
113
|
+
suite.fixture_classes.add(fixture_class)
|
|
114
|
+
suite.tests.add(SmartestSelfTest.test_case("first", proc { |greeting:| expect(greeting).to eq("Hello, Alice") }))
|
|
115
|
+
suite.tests.add(SmartestSelfTest.test_case("second", proc { |greeting:| expect(greeting).to eq("Hello, Alice") }))
|
|
116
|
+
|
|
117
|
+
status, = SmartestSelfTest.run_suite(suite)
|
|
118
|
+
|
|
119
|
+
expect(status).to eq(0)
|
|
120
|
+
expect(calls).to eq(%i[user user])
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
test("creates fresh fixture values and fixture instances for each test") do
|
|
124
|
+
markers = []
|
|
125
|
+
instances = []
|
|
126
|
+
|
|
127
|
+
fixture_class = Class.new(Smartest::Fixture) do
|
|
128
|
+
fixture :marker do
|
|
129
|
+
instances << self
|
|
130
|
+
Object.new
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
suite = Smartest::Suite.new
|
|
135
|
+
suite.fixture_classes.add(fixture_class)
|
|
136
|
+
suite.tests.add(SmartestSelfTest.test_case("first", proc { |marker:| markers << marker }))
|
|
137
|
+
suite.tests.add(SmartestSelfTest.test_case("second", proc { |marker:| markers << marker }))
|
|
138
|
+
|
|
139
|
+
status, = SmartestSelfTest.run_suite(suite)
|
|
140
|
+
|
|
141
|
+
expect(status).to eq(0)
|
|
142
|
+
expect(markers.length).to eq(2)
|
|
143
|
+
expect(markers[0].object_id).not_to eq(markers[1].object_id)
|
|
144
|
+
expect(instances.length).to eq(2)
|
|
145
|
+
expect(instances[0].object_id).not_to eq(instances[1].object_id)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
test("caches fixture values within one test") do
|
|
149
|
+
calls = 0
|
|
150
|
+
|
|
151
|
+
fixture_class = Class.new(Smartest::Fixture) do
|
|
152
|
+
fixture :token do
|
|
153
|
+
calls += 1
|
|
154
|
+
Object.new
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
fixture :first do |token:|
|
|
158
|
+
token
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
fixture :second do |token:|
|
|
162
|
+
token
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
suite = Smartest::Suite.new
|
|
167
|
+
suite.fixture_classes.add(fixture_class)
|
|
168
|
+
suite.tests.add(SmartestSelfTest.test_case("same object", proc { |first:, second:| expect(first.object_id).to eq(second.object_id) }))
|
|
169
|
+
|
|
170
|
+
status, = SmartestSelfTest.run_suite(suite)
|
|
171
|
+
|
|
172
|
+
expect(status).to eq(0)
|
|
173
|
+
expect(calls).to eq(1)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
test("suite fixtures are created once and cleaned up after the suite") do
|
|
177
|
+
events = []
|
|
178
|
+
servers = []
|
|
179
|
+
|
|
180
|
+
fixture_class = Class.new(Smartest::Fixture) do
|
|
181
|
+
suite_fixture :server do
|
|
182
|
+
events << :server_setup
|
|
183
|
+
server = Object.new
|
|
184
|
+
cleanup { events << :server_cleanup }
|
|
185
|
+
server
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
suite = Smartest::Suite.new
|
|
190
|
+
suite.fixture_classes.add(fixture_class)
|
|
191
|
+
suite.tests.add(SmartestSelfTest.test_case("first", proc { |server:| events << :first; servers << server }))
|
|
192
|
+
suite.tests.add(SmartestSelfTest.test_case("second", proc { |server:| events << :second; servers << server }))
|
|
193
|
+
|
|
194
|
+
status, = SmartestSelfTest.run_suite(suite)
|
|
195
|
+
|
|
196
|
+
expect(status).to eq(0)
|
|
197
|
+
expect(events).to eq(%i[server_setup first second server_cleanup])
|
|
198
|
+
expect(servers.length).to eq(2)
|
|
199
|
+
expect(servers[0].object_id).to eq(servers[1].object_id)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
test("test fixtures can depend on suite fixtures") do
|
|
203
|
+
calls = []
|
|
204
|
+
server_ids = []
|
|
205
|
+
client_ids = []
|
|
206
|
+
|
|
207
|
+
fixture_class = Class.new(Smartest::Fixture) do
|
|
208
|
+
suite_fixture :server do
|
|
209
|
+
calls << :server
|
|
210
|
+
Object.new
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
fixture :client do |server:|
|
|
214
|
+
[server, Object.new]
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
suite = Smartest::Suite.new
|
|
219
|
+
suite.fixture_classes.add(fixture_class)
|
|
220
|
+
suite.tests.add(SmartestSelfTest.test_case("first", proc { |client:| server_ids << client.first.object_id; client_ids << client.last.object_id }))
|
|
221
|
+
suite.tests.add(SmartestSelfTest.test_case("second", proc { |client:| server_ids << client.first.object_id; client_ids << client.last.object_id }))
|
|
222
|
+
|
|
223
|
+
status, = SmartestSelfTest.run_suite(suite)
|
|
224
|
+
|
|
225
|
+
expect(status).to eq(0)
|
|
226
|
+
expect(calls).to eq([:server])
|
|
227
|
+
expect(server_ids.uniq.length).to eq(1)
|
|
228
|
+
expect(client_ids.uniq.length).to eq(2)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
test("suite fixtures cannot depend on test fixtures") do
|
|
232
|
+
fixture_class = Class.new(Smartest::Fixture) do
|
|
233
|
+
fixture :user do
|
|
234
|
+
:user
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
suite_fixture :server do |user:|
|
|
238
|
+
user
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
suite = Smartest::Suite.new
|
|
243
|
+
suite.fixture_classes.add(fixture_class)
|
|
244
|
+
suite.tests.add(SmartestSelfTest.test_case("needs server", proc { |server:| expect(server).to eq(:server) }))
|
|
245
|
+
|
|
246
|
+
status, output = SmartestSelfTest.run_suite(suite)
|
|
247
|
+
|
|
248
|
+
expect(status).to eq(1)
|
|
249
|
+
expect(output).to include("suite-scoped fixture server cannot depend on test-scoped fixture user")
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
test("suite fixture setup failures are cached and cleaned up once") do
|
|
253
|
+
calls = 0
|
|
254
|
+
events = []
|
|
255
|
+
|
|
256
|
+
fixture_class = Class.new(Smartest::Fixture) do
|
|
257
|
+
suite_fixture :server do
|
|
258
|
+
calls += 1
|
|
259
|
+
cleanup { events << :server_cleanup }
|
|
260
|
+
raise "server setup failed"
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
suite = Smartest::Suite.new
|
|
265
|
+
suite.fixture_classes.add(fixture_class)
|
|
266
|
+
suite.tests.add(SmartestSelfTest.test_case("first", proc { |server:| expect(server).to eq(:server) }))
|
|
267
|
+
suite.tests.add(SmartestSelfTest.test_case("second", proc { |server:| expect(server).to eq(:server) }))
|
|
268
|
+
|
|
269
|
+
status, output = SmartestSelfTest.run_suite(suite)
|
|
270
|
+
|
|
271
|
+
expect(status).to eq(1)
|
|
272
|
+
expect(calls).to eq(1)
|
|
273
|
+
expect(events).to eq([:server_cleanup])
|
|
274
|
+
expect(output.scan("RuntimeError: server setup failed").length).to eq(2)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
test("suite cleanup failures fail the run") do
|
|
278
|
+
fixture_class = Class.new(Smartest::Fixture) do
|
|
279
|
+
suite_fixture :browser do
|
|
280
|
+
cleanup { raise "browser close failed" }
|
|
281
|
+
:browser
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
suite = Smartest::Suite.new
|
|
286
|
+
suite.fixture_classes.add(fixture_class)
|
|
287
|
+
suite.tests.add(SmartestSelfTest.test_case("uses browser", proc { |browser:| expect(browser).to eq(:browser) }))
|
|
288
|
+
|
|
289
|
+
status, output = SmartestSelfTest.run_suite(suite)
|
|
290
|
+
|
|
291
|
+
expect(status).to eq(1)
|
|
292
|
+
expect(output).to include("Suite cleanup failures:")
|
|
293
|
+
expect(output).to include("cleanup failed: RuntimeError: browser close failed")
|
|
294
|
+
expect(output).to include("1 test, 1 passed, 0 failed, 1 suite cleanup failed")
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
test("runs cleanup in reverse order after failures") do
|
|
298
|
+
events = []
|
|
299
|
+
|
|
300
|
+
fixture_class = Class.new(Smartest::Fixture) do
|
|
301
|
+
fixture :server do
|
|
302
|
+
cleanup { events << :server }
|
|
303
|
+
:server
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
fixture :browser do |server:|
|
|
307
|
+
cleanup { events << :browser }
|
|
308
|
+
server
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
suite = Smartest::Suite.new
|
|
313
|
+
suite.fixture_classes.add(fixture_class)
|
|
314
|
+
suite.tests.add(SmartestSelfTest.test_case("fails", proc { |browser:| expect(browser).to eq(:nope) }))
|
|
315
|
+
|
|
316
|
+
status, = SmartestSelfTest.run_suite(suite)
|
|
317
|
+
|
|
318
|
+
expect(status).to eq(1)
|
|
319
|
+
expect(events).to eq(%i[browser server])
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
test("runs cleanup when fixture setup fails after cleanup registration") do
|
|
323
|
+
events = []
|
|
324
|
+
|
|
325
|
+
fixture_class = Class.new(Smartest::Fixture) do
|
|
326
|
+
fixture :server do
|
|
327
|
+
cleanup { events << :server }
|
|
328
|
+
raise "server setup failed"
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
suite = Smartest::Suite.new
|
|
333
|
+
suite.fixture_classes.add(fixture_class)
|
|
334
|
+
suite.tests.add(SmartestSelfTest.test_case("needs server", proc { |server:| expect(server).to eq(:server) }))
|
|
335
|
+
|
|
336
|
+
status, output = SmartestSelfTest.run_suite(suite)
|
|
337
|
+
|
|
338
|
+
expect(status).to eq(1)
|
|
339
|
+
expect(events).to eq([:server])
|
|
340
|
+
expect(output).to include("RuntimeError: server setup failed")
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
test("duplicate fixture names fail the test") do
|
|
344
|
+
first_fixture = Class.new(Smartest::Fixture) do
|
|
345
|
+
fixture(:user) { "Alice" }
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
second_fixture = Class.new(Smartest::Fixture) do
|
|
349
|
+
fixture(:user) { "Bob" }
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
suite = Smartest::Suite.new
|
|
353
|
+
suite.fixture_classes.add(first_fixture)
|
|
354
|
+
suite.fixture_classes.add(second_fixture)
|
|
355
|
+
suite.tests.add(SmartestSelfTest.test_case("needs user", proc { |user:| expect(user).to eq("Alice") }))
|
|
356
|
+
|
|
357
|
+
status, output = SmartestSelfTest.run_suite(suite)
|
|
358
|
+
|
|
359
|
+
expect(status).to eq(1)
|
|
360
|
+
expect(output).to include("duplicate fixture: user")
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
test("allows child fixture classes to override parent fixtures") do
|
|
364
|
+
parent_fixture = Class.new(Smartest::Fixture) do
|
|
365
|
+
fixture(:user_name) { "Parent" }
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
child_fixture = Class.new(parent_fixture) do
|
|
369
|
+
fixture(:user_name) { "Child" }
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
suite = Smartest::Suite.new
|
|
373
|
+
suite.fixture_classes.add(child_fixture)
|
|
374
|
+
suite.tests.add(SmartestSelfTest.test_case("uses override", proc { |user_name:| expect(user_name).to eq("Child") }))
|
|
375
|
+
|
|
376
|
+
status, = SmartestSelfTest.run_suite(suite)
|
|
377
|
+
|
|
378
|
+
expect(status).to eq(0)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
test("circular fixture dependencies fail the test") do
|
|
382
|
+
fixture_class = Class.new(Smartest::Fixture) do
|
|
383
|
+
fixture(:a) { |b:| b }
|
|
384
|
+
fixture(:b) { |a:| a }
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
suite = Smartest::Suite.new
|
|
388
|
+
suite.fixture_classes.add(fixture_class)
|
|
389
|
+
suite.tests.add(SmartestSelfTest.test_case("cycle", proc { |a:| expect(a).to eq(:a) }))
|
|
390
|
+
|
|
391
|
+
status, output = SmartestSelfTest.run_suite(suite)
|
|
392
|
+
|
|
393
|
+
expect(status).to eq(1)
|
|
394
|
+
expect(output).to include("circular fixture dependency: a -> b -> a")
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
test("fixture blocks can call private helper methods") do
|
|
398
|
+
fixture_class = Class.new(Smartest::Fixture) do
|
|
399
|
+
fixture :user_name do
|
|
400
|
+
build_user_name("Alice")
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
private
|
|
404
|
+
|
|
405
|
+
def build_user_name(name)
|
|
406
|
+
name.upcase
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
suite = Smartest::Suite.new
|
|
411
|
+
suite.fixture_classes.add(fixture_class)
|
|
412
|
+
suite.tests.add(SmartestSelfTest.test_case("uses helper", proc { |user_name:| expect(user_name).to eq("ALICE") }))
|
|
413
|
+
|
|
414
|
+
status, = SmartestSelfTest.run_suite(suite)
|
|
415
|
+
|
|
416
|
+
expect(status).to eq(0)
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
test("rejects positional test parameters") do
|
|
420
|
+
error = SmartestSelfTest.capture_error(Smartest::InvalidFixtureParameterError) do
|
|
421
|
+
SmartestSelfTest.test_case("bad", proc { |_user| nil })
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
expect(error.message).to include("Positional fixture parameters are not supported.")
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
test("rejects positional fixture parameters") do
|
|
428
|
+
error = SmartestSelfTest.capture_error(Smartest::InvalidFixtureParameterError) do
|
|
429
|
+
Class.new(Smartest::Fixture) do
|
|
430
|
+
fixture(:bad) { |_server| nil }
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
expect(error.message).to include("Positional fixture dependencies are not supported.")
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
test("rejects invalid fixture scopes") do
|
|
438
|
+
error = SmartestSelfTest.capture_error(Smartest::InvalidFixtureScopeError) do
|
|
439
|
+
Class.new(Smartest::Fixture) do
|
|
440
|
+
fixture(:bad, scope: :file) { nil }
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
expect(error.message).to include("invalid fixture scope: :file")
|
|
445
|
+
expect(error.message).to include("supported scopes: test, suite")
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
test("cli loads files and returns failure status") do
|
|
449
|
+
Dir.mktmpdir do |dir|
|
|
450
|
+
smartest_dir = File.join(dir, "smartest")
|
|
451
|
+
FileUtils.mkdir_p(smartest_dir)
|
|
452
|
+
File.write(File.join(smartest_dir, "test_helper.rb"), <<~RUBY)
|
|
453
|
+
require "smartest/autorun"
|
|
454
|
+
RUBY
|
|
455
|
+
|
|
456
|
+
test_file = File.join(smartest_dir, "sample_test.rb")
|
|
457
|
+
File.write(test_file, <<~RUBY)
|
|
458
|
+
require "test_helper"
|
|
459
|
+
|
|
460
|
+
test("cli failure") do
|
|
461
|
+
expect("a").to eq("b")
|
|
462
|
+
end
|
|
463
|
+
RUBY
|
|
464
|
+
|
|
465
|
+
stdout, stderr, status = Open3.capture3(
|
|
466
|
+
{ "RUBYLIB" => File.expand_path("../lib", __dir__) },
|
|
467
|
+
"ruby",
|
|
468
|
+
File.expand_path("../exe/smartest", __dir__),
|
|
469
|
+
"smartest/sample_test.rb",
|
|
470
|
+
chdir: dir
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
expect(status.success?).to eq(false)
|
|
474
|
+
expect(stderr).to eq("")
|
|
475
|
+
expect(stdout).to include("cli failure")
|
|
476
|
+
expect(stdout).to include("expected \"a\" to eq \"b\"")
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
test("cli default suite ignores minitest-style test directory") do
|
|
481
|
+
Dir.mktmpdir do |dir|
|
|
482
|
+
smartest_dir = File.join(dir, "smartest")
|
|
483
|
+
FileUtils.mkdir_p(smartest_dir)
|
|
484
|
+
File.write(File.join(smartest_dir, "test_helper.rb"), <<~RUBY)
|
|
485
|
+
require "smartest/autorun"
|
|
486
|
+
RUBY
|
|
487
|
+
|
|
488
|
+
File.write(File.join(smartest_dir, "sample_test.rb"), <<~RUBY)
|
|
489
|
+
require "test_helper"
|
|
490
|
+
|
|
491
|
+
test("smartest default") do
|
|
492
|
+
expect(1).to eq(1)
|
|
493
|
+
end
|
|
494
|
+
RUBY
|
|
495
|
+
|
|
496
|
+
minitest_dir = File.join(dir, "test")
|
|
497
|
+
FileUtils.mkdir_p(minitest_dir)
|
|
498
|
+
File.write(File.join(minitest_dir, "test_helper.rb"), <<~RUBY)
|
|
499
|
+
raise "loaded minitest helper"
|
|
500
|
+
RUBY
|
|
501
|
+
File.write(File.join(minitest_dir, "sample_test.rb"), <<~RUBY)
|
|
502
|
+
raise "loaded minitest test"
|
|
503
|
+
RUBY
|
|
504
|
+
|
|
505
|
+
stdout, stderr, status = Open3.capture3(
|
|
506
|
+
{ "RUBYLIB" => File.expand_path("../lib", __dir__) },
|
|
507
|
+
"ruby",
|
|
508
|
+
File.expand_path("../exe/smartest", __dir__),
|
|
509
|
+
chdir: dir
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
expect(status.success?).to eq(true)
|
|
513
|
+
expect(stderr).to eq("")
|
|
514
|
+
expect(stdout).to include("smartest default")
|
|
515
|
+
expect(stdout).to include("1 test, 1 passed, 0 failed")
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
test("cli prints version") do
|
|
520
|
+
stdout, stderr, status = Open3.capture3(
|
|
521
|
+
{ "RUBYLIB" => File.expand_path("../lib", __dir__) },
|
|
522
|
+
"ruby",
|
|
523
|
+
File.expand_path("../exe/smartest", __dir__),
|
|
524
|
+
"--version"
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
expect(status.success?).to eq(true)
|
|
528
|
+
expect(stderr).to eq("")
|
|
529
|
+
expect(stdout).to eq("#{Smartest::VERSION}\n")
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
test("cli prints help") do
|
|
533
|
+
stdout, stderr, status = Open3.capture3(
|
|
534
|
+
{ "RUBYLIB" => File.expand_path("../lib", __dir__) },
|
|
535
|
+
"ruby",
|
|
536
|
+
File.expand_path("../exe/smartest", __dir__),
|
|
537
|
+
"--help"
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
expect(status.success?).to eq(true)
|
|
541
|
+
expect(stderr).to eq("")
|
|
542
|
+
expect(stdout).to include("Usage:")
|
|
543
|
+
expect(stdout).to include("smartest [paths...]")
|
|
544
|
+
expect(stdout).to include("smartest --init")
|
|
545
|
+
expect(stdout).to include("smartest/**/*_test.rb")
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
test("cli initializes a runnable test scaffold") do
|
|
549
|
+
Dir.mktmpdir do |dir|
|
|
550
|
+
stdout, stderr, status = Open3.capture3(
|
|
551
|
+
{ "RUBYLIB" => File.expand_path("../lib", __dir__) },
|
|
552
|
+
"ruby",
|
|
553
|
+
File.expand_path("../exe/smartest", __dir__),
|
|
554
|
+
"--init",
|
|
555
|
+
chdir: dir
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
expect(status.success?).to eq(true)
|
|
559
|
+
expect(stderr).to eq("")
|
|
560
|
+
expect(stdout).to include("create smartest")
|
|
561
|
+
expect(stdout).to include("create smartest/fixtures")
|
|
562
|
+
expect(stdout).to include("create smartest/test_helper.rb")
|
|
563
|
+
expect(stdout).to include("create smartest/example_test.rb")
|
|
564
|
+
helper_contents = File.read(File.join(dir, "smartest/test_helper.rb"))
|
|
565
|
+
expect(helper_contents).to include('require "smartest/autorun"')
|
|
566
|
+
expect(helper_contents).to include('Dir[File.join(__dir__, "fixtures", "**", "*.rb")].sort.each')
|
|
567
|
+
expect(File.read(File.join(dir, "smartest/example_test.rb"))).to include('require "test_helper"')
|
|
568
|
+
|
|
569
|
+
nested_fixtures_dir = File.join(dir, "smartest/fixtures/nested")
|
|
570
|
+
FileUtils.mkdir_p(nested_fixtures_dir)
|
|
571
|
+
File.write(File.join(nested_fixtures_dir, "auto_loaded_fixture.rb"), <<~RUBY)
|
|
572
|
+
class AutoLoadedFixture < Smartest::Fixture
|
|
573
|
+
fixture :auto_loaded_message do
|
|
574
|
+
"loaded from smartest/fixtures"
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
RUBY
|
|
578
|
+
|
|
579
|
+
File.write(File.join(dir, "smartest/auto_loaded_fixture_test.rb"), <<~RUBY)
|
|
580
|
+
require "test_helper"
|
|
581
|
+
|
|
582
|
+
use_fixture AutoLoadedFixture
|
|
583
|
+
|
|
584
|
+
test("auto-loaded fixture") do |auto_loaded_message:|
|
|
585
|
+
expect(auto_loaded_message).to eq("loaded from smartest/fixtures")
|
|
586
|
+
end
|
|
587
|
+
RUBY
|
|
588
|
+
|
|
589
|
+
run_stdout, run_stderr, run_status = Open3.capture3(
|
|
590
|
+
{ "RUBYLIB" => File.expand_path("../lib", __dir__) },
|
|
591
|
+
"ruby",
|
|
592
|
+
File.expand_path("../exe/smartest", __dir__),
|
|
593
|
+
chdir: dir
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
expect(run_status.success?).to eq(true)
|
|
597
|
+
expect(run_stderr).to eq("")
|
|
598
|
+
expect(run_stdout).to include("example")
|
|
599
|
+
expect(run_stdout).to include("auto-loaded fixture")
|
|
600
|
+
expect(run_stdout).to include("2 tests, 2 passed, 0 failed")
|
|
601
|
+
end
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
test("cli init does not overwrite existing scaffold files") do
|
|
605
|
+
Dir.mktmpdir do |dir|
|
|
606
|
+
smartest_dir = File.join(dir, "smartest")
|
|
607
|
+
fixture_dir = File.join(smartest_dir, "fixtures")
|
|
608
|
+
FileUtils.mkdir_p(fixture_dir)
|
|
609
|
+
helper_path = File.join(smartest_dir, "test_helper.rb")
|
|
610
|
+
example_path = File.join(smartest_dir, "example_test.rb")
|
|
611
|
+
fixture_path = File.join(fixture_dir, "custom_fixture.rb")
|
|
612
|
+
File.write(helper_path, "# custom helper\n")
|
|
613
|
+
File.write(example_path, "# custom test\n")
|
|
614
|
+
File.write(fixture_path, "# custom fixture\n")
|
|
615
|
+
|
|
616
|
+
stdout, stderr, status = Open3.capture3(
|
|
617
|
+
{ "RUBYLIB" => File.expand_path("../lib", __dir__) },
|
|
618
|
+
"ruby",
|
|
619
|
+
File.expand_path("../exe/smartest", __dir__),
|
|
620
|
+
"--init",
|
|
621
|
+
chdir: dir
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
expect(status.success?).to eq(true)
|
|
625
|
+
expect(stderr).to eq("")
|
|
626
|
+
expect(stdout).to include("exist smartest")
|
|
627
|
+
expect(stdout).to include("exist smartest/fixtures")
|
|
628
|
+
expect(stdout).to include("exist smartest/test_helper.rb")
|
|
629
|
+
expect(stdout).to include("exist smartest/example_test.rb")
|
|
630
|
+
expect(File.read(helper_path)).to eq("# custom helper\n")
|
|
631
|
+
expect(File.read(example_path)).to eq("# custom test\n")
|
|
632
|
+
expect(File.read(fixture_path)).to eq("# custom fixture\n")
|
|
633
|
+
end
|
|
634
|
+
end
|
data/smartest.gemspec
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/smartest/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "smartest"
|
|
7
|
+
spec.version = Smartest::VERSION
|
|
8
|
+
spec.authors = ["Yusuke Iwaki"]
|
|
9
|
+
|
|
10
|
+
spec.summary = "A small Ruby test runner with keyword-first fixtures."
|
|
11
|
+
spec.description = "Smartest is a small Ruby test runner focused on readable top-level tests, explicit keyword-argument fixture dependencies, and optional fixture cleanup."
|
|
12
|
+
spec.homepage = "https://github.com/YusukeIwaki/smartest"
|
|
13
|
+
spec.license = "MIT"
|
|
14
|
+
spec.required_ruby_version = ">= 3.1"
|
|
15
|
+
|
|
16
|
+
spec.metadata = {
|
|
17
|
+
"allowed_push_host" => "https://rubygems.org",
|
|
18
|
+
"bug_tracker_uri" => "https://github.com/YusukeIwaki/smartest/issues",
|
|
19
|
+
"documentation_uri" => "https://smartest-rb.vercel.app/",
|
|
20
|
+
"homepage_uri" => spec.homepage,
|
|
21
|
+
"rubygems_mfa_required" => "true",
|
|
22
|
+
"source_code_uri" => "https://github.com/YusukeIwaki/smartest/tree/main"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
spec.files = Dir.chdir(__dir__) do
|
|
26
|
+
Dir.glob(
|
|
27
|
+
[
|
|
28
|
+
"CHANGELOG.md",
|
|
29
|
+
"DEVELOPMENT.md",
|
|
30
|
+
"Gemfile",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"README.md",
|
|
33
|
+
"Rakefile",
|
|
34
|
+
"SMARTEST_DESIGN.md",
|
|
35
|
+
"exe/*",
|
|
36
|
+
"lib/**/*.rb",
|
|
37
|
+
"smartest.gemspec",
|
|
38
|
+
"smartest/**/*.rb"
|
|
39
|
+
]
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
spec.bindir = "exe"
|
|
44
|
+
spec.executables = ["smartest"]
|
|
45
|
+
spec.require_paths = ["lib"]
|
|
46
|
+
|
|
47
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
48
|
+
end
|