test_construct 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ # TestConstruct Changelog
2
+
3
+ ## v2.0.0
4
+
5
+ Enhancements:
6
+
7
+ * Add RSpec 2 support (@avdi)
8
+ * Option to persist directories after test run or after failure (@avdi)
9
+ * Add naming of created directories (@avdi)
10
+
11
+ Other:
12
+
13
+ * Non-backwards-compatible change to `TestConstruct::Helpers#create_construct` method signature (takes option hash now)
data/README.md CHANGED
@@ -142,6 +142,79 @@ within_construct(:chdir => false) do |construct|
142
142
  end
143
143
  ```
144
144
 
145
+ ### Keeping directories around
146
+
147
+ You may find it convenient to keep the created directory around after a test has completed, in order to manually inspect its contents.
148
+
149
+ ```ruby
150
+ within_construct do |construct|
151
+ # ...
152
+ construct.keep
153
+ end
154
+ ```
155
+
156
+ Most likely you only want the directory to stick around if something goes wrong. To do this, use the `:keep_on_error` option.
157
+
158
+ ```ruby
159
+ within_construct(keep_on_error: true) do |construct|
160
+ # ...
161
+ raise "some error"
162
+ end
163
+ ```
164
+
165
+ TestConstruct will also annotate the exception error message to tell you where the generated files can be found.
166
+
167
+ ### Setting the base directory
168
+
169
+ By default, TestConstruct puts its temporary container directories in your system temp dir. You can change this with the `:base_dir` option:
170
+
171
+ ```ruby
172
+ tmp_dir = File.expand_path("../../tmp", __FILE__)
173
+ within_construct(base_dir: tmp_dir) do |construct|
174
+ construct.file("foo.txt")
175
+ # Passes
176
+ assert File.exists?(tmp_dir+"/foo.txt")
177
+ end
178
+ ```
179
+
180
+ ### Naming the created directories
181
+
182
+ Normally TestConstruct names the container directories it creates using a combination of a `test-construct-` prefix, the current process ID, and a random number. This ensures that the name is unlikely to clash with directories left over from previous runs. However, it isn't very meaningful. You can optionally make the directory names more recognizable by specifying a `:name` option. TestConstruct will take the string passed, turn it into a normalized "slug" without any funny characters, and append it to the end of the generated dirname.
183
+
184
+ ```ruby
185
+ within_construct(name: "My best test ever!") do |construct|
186
+ # will generate something like:
187
+ # /tmp/construct-container-1234-5678-my-best-test-ever
188
+ end
189
+ ```
190
+
191
+ ### RSpec Integration
192
+
193
+ TestConstruct comes with RSpec integration. Just require the `test_construct/rspec_integration` file in your `spec_helper.rb` or in your spec file. Then tag the tests you want to execute in the context of a construct container with `test_construct: true` using RSpec metadata:
194
+
195
+ ```ruby
196
+ require "test_construct/rspec_integration"
197
+
198
+ describe Foo, test_construct: true do
199
+ it "should do stuff" do
200
+ example.metadata[:construct].file "somefile"
201
+ example.metadata[:construct].directory "somedir"
202
+ # ...
203
+ end
204
+ end
205
+ ```
206
+
207
+ By default, the current working directory will be switched to the construct container within tests; the container name will be derived from the name of the current example; and if a test fails, the container will be kept around. Information about where to find it will be added to the test failure message.
208
+
209
+ You can tweak any TestConstruct options by passing a hash as the value of the `:test_construct` metadata key.
210
+
211
+ ```ruby
212
+ require "test_construct/rspec_integration"
213
+
214
+ describe Foo, test_construct: {keep_on_error: false} do
215
+ # ...
216
+ end
217
+ ```
145
218
 
146
219
  ## Contributing
147
220
 
@@ -0,0 +1,34 @@
1
+ # Run with:
2
+ # rspec -Ilib examples/foobar_spec.rb
3
+
4
+ require 'rspec'
5
+ require 'test_construct/rspec_integration'
6
+
7
+ describe "Foobar", test_construct: true do
8
+
9
+ it "creates file" do
10
+ example.metadata[:construct].directory "alice/rabbithole" do |d|
11
+ d.file "white_rabbit.txt", "I'm late!"
12
+ File.read("white_rabbit.txt").should == "I'm late!"
13
+ end
14
+ end
15
+
16
+ it "leaves file on error" do
17
+ example.metadata[:construct].directory "alice/rabbithole" do |d|
18
+ d.file "white_rabbit.txt", "I'm late!"
19
+ raise "Whoops"
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ describe "Foobar", test_construct: { keep_on_error: false} do
26
+
27
+ it "doesn't leave file on error" do
28
+ example.metadata[:construct].directory "alice/rabbithole" do |d|
29
+ d.file "white_rabbit.txt", "I'm late!"
30
+ raise "Whoops"
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,38 @@
1
+ # Run with:
2
+ # ruby -Ilib examples/foobar_test.rb
3
+
4
+ require 'test_construct'
5
+ require 'test/unit'
6
+
7
+ class FoobarTest < Test::Unit::TestCase
8
+ include TestConstruct::Helpers
9
+
10
+ def test_directory_and_files
11
+ within_construct do |c|
12
+ c.directory 'alice/rabbithole' do |d|
13
+ d.file 'white_rabbit.txt', "I'm late!"
14
+
15
+ assert_equal "I'm late!", File.read('white_rabbit.txt')
16
+ end
17
+ end
18
+ end
19
+
20
+ def test_keeping_directory_on_error
21
+ within_construct(keep_on_error: true) do |c|
22
+ c.directory 'd' do |d|
23
+ d.file 'doughnut.txt'
24
+ raise "whoops"
25
+ end
26
+ end
27
+ end
28
+
29
+ def test_deleting_directory_on_error
30
+ within_construct(keep_on_error: false) do |c|
31
+ c.directory 'd' do |d|
32
+ d.file 'doughnut.txt'
33
+ raise "whoops"
34
+ end
35
+ end
36
+ end
37
+
38
+ end
@@ -1,27 +1,66 @@
1
+ require 'English'
2
+ require 'pathname'
3
+
1
4
  module TestConstruct
2
5
 
3
6
  module Helpers
4
7
  extend self
5
8
 
6
9
  def within_construct(opts = {})
7
- chdir = opts.fetch(:chdir, true)
8
- container = create_construct(chdir)
9
- container.maybe_change_dir(chdir) do
10
- yield(container)
11
- end
12
- ensure
13
- container.destroy! if container.respond_to?(:destroy!)
10
+ container = setup_construct(opts)
11
+ yield(container)
12
+ rescue Exception => error
13
+ raise unless container
14
+ teardown_construct(container, error, opts)
15
+ raise error
16
+ else
17
+ teardown_construct(container, nil, opts)
14
18
  end
15
19
 
16
- def create_construct(chdir = true)
17
- path = (Pathname(TestConstruct.tmpdir) +
18
- "#{CONTAINER_PREFIX}-#{$PROCESS_ID}-#{rand(1_000_000_000)}")
20
+ def create_construct(opts = {})
21
+ chdir_default = opts.delete(:chdir) { true }
22
+ base_path = Pathname(opts.delete(:base_dir) { TestConstruct.tmpdir })
23
+ name = opts.delete(:name) { "" }
24
+ slug = name.downcase.tr_s("^a-z0-9", "-")[0..63]
25
+ if opts.any?
26
+ raise "[TestConstruct] Unrecognized options: #{opts.keys}"
27
+ end
28
+ dir = "#{CONTAINER_PREFIX}-#{$PROCESS_ID}-#{rand(1_000_000_000)}"
29
+ dir << "-" << slug unless slug.empty?
30
+ path = base_path + dir
19
31
  path.mkpath
20
32
  path.extend(PathnameExtensions)
21
- path.construct__chdir_default = chdir
33
+ path.construct__chdir_default = chdir_default
22
34
  path
23
35
  end
24
36
 
37
+ # THIS METHOD MAY HAVE EXTERNAL SIDE-EFFECTS, including:
38
+ # - creating the container directory tree
39
+ # - changing the current working directory
40
+ #
41
+ # It is intended to be paired with #teardown_construct
42
+ def setup_construct(opts = {})
43
+ opts = opts.dup
44
+ chdir = opts.fetch(:chdir, true)
45
+ opts.delete(:keep_on_error) { false } # not used in setup
46
+ container = create_construct(opts)
47
+ container.maybe_change_dir(chdir)
48
+ container
49
+ end
50
+
51
+ # THIS METHOD MAY HAVE EXTERNAL SIDE-EFFECTS, including:
52
+ # - removing the container directory tree
53
+ # - changing the current working directory
54
+ # - modifying any exception passed as `error`
55
+ #
56
+ # It is intended to be paired with #setup_construct
57
+ def teardown_construct(container, error = nil, opts = {})
58
+ if error && opts[:keep_on_error]
59
+ container.keep
60
+ container.annotate_exception!(error)
61
+ end
62
+ container.finalize
63
+ end
25
64
  end
26
65
 
27
66
  extend Helpers
@@ -1,13 +1,13 @@
1
1
  module TestConstruct
2
2
  module PathnameExtensions
3
3
 
4
- attr_accessor :construct__chdir_default
5
-
4
+ attr_accessor :construct__chdir_default, :construct__root, :construct__orig_dir
6
5
  def directory(path, opts = {})
7
6
  chdir = opts.fetch(:chdir, construct__chdir_default)
8
7
  subdir = (self + path)
9
8
  subdir.mkpath
10
9
  subdir.extend(PathnameExtensions)
10
+ subdir.construct__root = construct__root || self
11
11
  subdir.maybe_change_dir(chdir) do
12
12
  yield(subdir) if block_given?
13
13
  end
@@ -33,9 +33,16 @@ module TestConstruct
33
33
 
34
34
  def maybe_change_dir(chdir, &block)
35
35
  if(chdir)
36
+ self.construct__orig_dir ||= Pathname.pwd
36
37
  self.chdir(&block)
37
38
  else
38
- block.call
39
+ block.call if block
40
+ end
41
+ end
42
+
43
+ def revert_cwd
44
+ if construct__orig_dir
45
+ Dir.chdir(construct__orig_dir)
39
46
  end
40
47
  end
41
48
 
@@ -49,5 +56,30 @@ module TestConstruct
49
56
  rmtree
50
57
  end
51
58
 
59
+ def finalize
60
+ revert_cwd
61
+ destroy! unless keep?
62
+ end
63
+
64
+ def keep
65
+ if construct__root
66
+ construct__root.keep
67
+ else
68
+ @keep = true
69
+ end
70
+ end
71
+
72
+ def keep?
73
+ defined?(@keep) && @keep
74
+ end
75
+
76
+ def annotate_exception!(error)
77
+ error.message << exception_message_annotation
78
+ error
79
+ end
80
+
81
+ def exception_message_annotation
82
+ "\nTestConstruct files kept at: #{self}"
83
+ end
52
84
  end
53
85
  end
@@ -0,0 +1,53 @@
1
+ require "test_construct"
2
+ require "rspec"
3
+ module TestConstruct
4
+ module RSpecIntegration
5
+ module_function
6
+
7
+ # the :test_construct metadata key can be either:
8
+ # - true (for all defaults)
9
+ # - a Hash of options
10
+ # - false/missing (disable the construct for this test)
11
+ def test_construct_options(example)
12
+ options = test_construct_default_options
13
+ options[:name] = example.full_description
14
+ metadata_options = example.metadata[:test_construct]
15
+ if metadata_options.is_a?(Hash)
16
+ options.merge!(metadata_options)
17
+ end
18
+ options
19
+ end
20
+
21
+ def test_construct_enabled?(example)
22
+ !!example.metadata[:test_construct]
23
+ end
24
+
25
+ def test_construct_default_options
26
+ {
27
+ base_dir: TestConstruct.tmpdir,
28
+ chdir: true,
29
+ keep_on_error: true,
30
+ }
31
+ end
32
+ end
33
+ end
34
+
35
+ RSpec.configure do |config|
36
+ config.include TestConstruct::Helpers
37
+ config.include TestConstruct::RSpecIntegration
38
+
39
+ config.before :each do
40
+ next unless test_construct_enabled?(example)
41
+ options = test_construct_options(example)
42
+ example.metadata[:construct] = setup_construct(options)
43
+ end
44
+
45
+ config.after :each do
46
+ next unless test_construct_enabled?(example)
47
+ options = test_construct_options(example)
48
+ teardown_construct(
49
+ example.metadata[:construct],
50
+ example.exception,
51
+ options)
52
+ end
53
+ end
@@ -1,3 +1,3 @@
1
1
  module TestConstruct
2
- VERSION = "1.0.0"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -4,6 +4,7 @@ class TestConstructTest < Minitest::Test
4
4
  include TestConstruct::Helpers
5
5
 
6
6
  def teardown
7
+ Dir.chdir File.expand_path("../..", __FILE__)
7
8
  TestConstruct.destroy_all!
8
9
  end
9
10
 
@@ -464,7 +465,6 @@ Contents
464
465
  end
465
466
 
466
467
  testing "#destroy!" do
467
-
468
468
  test "removes the construct container" do
469
469
  it = create_construct
470
470
  it.destroy!
@@ -472,4 +472,70 @@ Contents
472
472
  end
473
473
  end
474
474
 
475
+ testing "#finalize" do
476
+ test "removes the construct container" do
477
+ it = create_construct
478
+ it.finalize
479
+ assert !File.exist?(it.to_s)
480
+ end
481
+
482
+ test "leaves the container if keep is flagged" do
483
+ it = create_construct
484
+ it.keep
485
+ it.finalize
486
+ assert File.exist?(it.to_s)
487
+ end
488
+
489
+ test "leaves the container if keep is flagged in a subdir" do
490
+ it = create_construct
491
+ subdir = it.directory "subdir"
492
+ subdir.keep
493
+ it.finalize
494
+ assert File.exist?(it.to_s)
495
+ end
496
+ end
497
+
498
+ testing "keep_on_error = true" do
499
+ test 'keeps dir when block raises exception' do
500
+ path = nil
501
+ begin
502
+ within_construct(keep_on_error: true) do |container_path|
503
+ path = container_path
504
+ raise 'something bad happens here'
505
+ end
506
+ rescue
507
+ end
508
+ assert path.exist?
509
+ end
510
+
511
+ test 'updates exception message to include location of files' do
512
+ path = nil
513
+ begin
514
+ within_construct(keep_on_error: true) do |container_path|
515
+ path = container_path
516
+ raise 'bad stuff'
517
+ end
518
+ rescue => e
519
+ error = e
520
+ end
521
+ assert_equal "bad stuff\nTestConstruct files kept at: #{path}", e.message
522
+ end
523
+ end
524
+
525
+ testing 'base_dir option' do
526
+ test 'determines the location of construct dirs' do
527
+ base_dir = File.expand_path("../temp", __FILE__)
528
+ within_construct(base_dir: base_dir) do |container|
529
+ assert_equal base_dir, container.dirname.to_s
530
+ end
531
+ end
532
+ end
533
+
534
+ testing 'name option' do
535
+ test 'used in generation of the directory name' do
536
+ within_construct(name: "My best test ever!") do |container|
537
+ assert_match /my-best-test-ever-$/, container.basename.to_s
538
+ end
539
+ end
540
+ end
475
541
  end
@@ -23,4 +23,5 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "minitest", "~> 5.0.8"
24
24
  spec.add_development_dependency "mocha", "~> 0.14.0"
25
25
  spec.add_development_dependency "debugger"
26
+ spec.add_development_dependency "rspec"
26
27
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test_construct
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-12-04 00:00:00.000000000 Z
13
+ date: 2013-12-30 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler
@@ -92,6 +92,22 @@ dependencies:
92
92
  - - ! '>='
93
93
  - !ruby/object:Gem::Version
94
94
  version: '0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: rspec
97
+ requirement: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
95
111
  description: Creates temporary files and directories for testing.
96
112
  email:
97
113
  - ben@bbrinck.com
@@ -101,13 +117,17 @@ extensions: []
101
117
  extra_rdoc_files: []
102
118
  files:
103
119
  - .gitignore
120
+ - CHANGELOG.md
104
121
  - Gemfile
105
122
  - LICENSE.txt
106
123
  - README.md
107
124
  - Rakefile
125
+ - examples/foobar_spec.rb
126
+ - examples/foobar_test.rb
108
127
  - lib/test_construct.rb
109
128
  - lib/test_construct/helpers.rb
110
129
  - lib/test_construct/pathname_extensions.rb
130
+ - lib/test_construct/rspec_integration.rb
111
131
  - lib/test_construct/version.rb
112
132
  - test/test_construct_test.rb
113
133
  - test/test_helper.rb