snowglobe 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1a7cb4e42cb80539ad12a929b77cdbc345594797a830e1c84afc335c6f70db7d
4
+ data.tar.gz: 57224881549f30823d7e22b1c804e1d43a31cde9feadb159f437e09ac887e16b
5
+ SHA512:
6
+ metadata.gz: 154526eb1114b432086be4722e2b8e9e4d761a3850d60ea5e75465aca291b96722f1c450666496880f81f6f81369ae6e9cb5352705943645c566969218aab794
7
+ data.tar.gz: a260aa8b610ad5d94019b28947814ed728b5d7d2b3a8007ac62ed2ccb5af77f40391a91a3d2e730115f5f91a250e48af4ef56de78ee21242580ebe5144592ce1
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,132 @@
1
+ ---
2
+ AllCops:
3
+ Exclude:
4
+ - db/schema.rb
5
+ TargetRubyVersion: 2.5
6
+ Layout/AlignParameters:
7
+ EnforcedStyle: with_fixed_indentation
8
+ Layout/ClassStructure:
9
+ Enabled: true
10
+ Layout/DotPosition:
11
+ EnforcedStyle: trailing
12
+ Layout/InitialIndentation:
13
+ Enabled: false
14
+ Layout/IndentArray:
15
+ EnforcedStyle: "consistent"
16
+ Layout/IndentHash:
17
+ EnforcedStyle: "consistent"
18
+ Layout/IndentHeredoc:
19
+ Enabled: false
20
+ Layout/MultilineOperationIndentation:
21
+ EnforcedStyle: indented
22
+ Layout/MultilineMethodCallIndentation:
23
+ EnforcedStyle: indented
24
+ Layout/SpaceInLambdaLiteral:
25
+ Enabled: false
26
+ Layout/SpaceInsideStringInterpolation:
27
+ Enabled: false
28
+ Lint/AssignmentInCondition:
29
+ Enabled: false
30
+ Lint/HandleExceptions:
31
+ Enabled: false
32
+ Lint/RequireParentheses:
33
+ Enabled: false
34
+ Metrics/AbcSize:
35
+ Max: 32
36
+ Metrics/BlockLength:
37
+ ExcludedMethods:
38
+ - describe
39
+ - context
40
+ - it
41
+ - colored
42
+ Metrics/ClassLength:
43
+ Enabled: false
44
+ Metrics/LineLength:
45
+ Max: 80
46
+ IgnoredPatterns:
47
+ - "^\\s*describe"
48
+ - "^\\s*context"
49
+ - "^\\s*it"
50
+ Metrics/MethodLength:
51
+ Enabled: false
52
+ Metrics/ModuleLength:
53
+ Enabled: false
54
+ Metrics/ParameterLists:
55
+ CountKeywordArgs: false
56
+ Naming/MemoizedInstanceVariableName:
57
+ EnforcedStyleForLeadingUnderscores: required
58
+ Naming/PredicateName:
59
+ NamePrefixBlacklist:
60
+ - is_
61
+ Exclude:
62
+ - spec/**/*
63
+ Naming/UncommunicativeMethodParamName:
64
+ Enabled: false
65
+ Style/Alias:
66
+ Enabled: false
67
+ Style/BracesAroundHashParameters:
68
+ EnforcedStyle: context_dependent
69
+ Style/CollectionMethods:
70
+ Enabled: true
71
+ Style/Documentation:
72
+ Enabled: false
73
+ Style/EachWithObject:
74
+ Enabled: false
75
+ Style/EmptyMethod:
76
+ EnforcedStyle: expanded
77
+ Style/FormatString:
78
+ EnforcedStyle: percent
79
+ Style/FrozenStringLiteralComment:
80
+ Enabled: false
81
+ Style/GuardClause:
82
+ Enabled: false
83
+ Style/IfUnlessModifier:
84
+ Enabled: false
85
+ Style/Lambda:
86
+ Enabled: false
87
+ Style/LineEndConcatenation:
88
+ Enabled: false
89
+ Style/MethodCalledOnDoEndBlock:
90
+ Enabled: true
91
+ Style/MultilineBlockChain:
92
+ Enabled: false
93
+ Style/NegatedIf:
94
+ Enabled: false
95
+ Style/NegatedWhile:
96
+ Enabled: false
97
+ Style/OneLineConditional:
98
+ Enabled: false
99
+ Style/PercentLiteralDelimiters:
100
+ Enabled: false
101
+ Style/PreferredHashMethods:
102
+ Enabled: false
103
+ Style/RaiseArgs:
104
+ Enabled: false
105
+ Style/SafeNavigation:
106
+ Enabled: false
107
+ Style/Send:
108
+ Enabled: true
109
+ Style/SignalException:
110
+ Enabled: false
111
+ Style/SpecialGlobalVars:
112
+ Enabled: false
113
+ Style/StringLiterals:
114
+ EnforcedStyle: double_quotes
115
+ Style/StringLiteralsInInterpolation:
116
+ EnforcedStyle: double_quotes
117
+ Style/StringMethods:
118
+ Enabled: true
119
+ Style/SymbolArray:
120
+ Enabled: false
121
+ Style/TrailingCommaInArguments:
122
+ EnforcedStyleForMultiline: comma
123
+ Style/TrailingCommaInArrayLiteral:
124
+ EnforcedStyleForMultiline: comma
125
+ Style/TrailingCommaInHashLiteral:
126
+ EnforcedStyleForMultiline: comma
127
+ Style/UnneededCondition:
128
+ Enabled: false
129
+ Style/WhileUntilModifier:
130
+ Enabled: false
131
+ Style/WordArray:
132
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.1
7
+ before_install: gem install bundler -v 2.0.1
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ snowglobe (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.3)
10
+ rake (10.5.0)
11
+ rspec (3.8.0)
12
+ rspec-core (~> 3.8.0)
13
+ rspec-expectations (~> 3.8.0)
14
+ rspec-mocks (~> 3.8.0)
15
+ rspec-core (3.8.0)
16
+ rspec-support (~> 3.8.0)
17
+ rspec-expectations (3.8.2)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.8.0)
20
+ rspec-mocks (3.8.0)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.8.0)
23
+ rspec-support (3.8.0)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ bundler (~> 2.0)
30
+ rake (~> 10.0)
31
+ rspec (~> 3.0)
32
+ snowglobe!
33
+
34
+ BUNDLED WITH
35
+ 2.0.1
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Elliot Winkler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # Snowglobe
2
+
3
+ Snowglobe is a gem that helps erect and destroy Rails applications for use in
4
+ tests.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your project's Gemfile:
9
+
10
+ ``` ruby
11
+ gem 'snowglobe'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ ``` bash
17
+ bundle
18
+ ```
19
+
20
+ Alternatively, install it yourself as:
21
+
22
+ ``` bash
23
+ gem install snowglobe
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ (To be filled in later)
29
+
30
+ ## Developing
31
+
32
+ * `bin/setup` to get started
33
+ * `bundle exec rake release` to release a new version
34
+
35
+ ## Author/License
36
+
37
+ Snowglobe is © 2019 Elliot Winkler (<elliot.winkler@gmail.com>) and is released
38
+ under the [MIT license](LICENSE).
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "snowglobe"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,93 @@
1
+ require_relative "filesystem"
2
+ require_relative "command_runner"
3
+ require_relative "gem_version"
4
+
5
+ module Snowglobe
6
+ class Bundle
7
+ def initialize(fs:, command_runner:)
8
+ @fs = fs
9
+ @command_runner = command_runner
10
+ @already_updating = false
11
+ end
12
+
13
+ def add_gem(gem, *args)
14
+ updating do
15
+ options = args.last.is_a?(Hash) ? args.pop : {}
16
+ version = args.shift
17
+ line = assemble_gem_line(gem, version, options)
18
+ fs.append_to_file("Gemfile", line)
19
+ end
20
+ end
21
+
22
+ def remove_gem(gem)
23
+ updating do
24
+ fs.comment_lines_matching_in_file("Gemfile", /^[ ]*gem ("|')#{gem}\1/)
25
+ end
26
+ end
27
+
28
+ def updating
29
+ if already_updating?
30
+ yield self
31
+ return
32
+ end
33
+
34
+ @already_updating = true
35
+
36
+ yield self
37
+
38
+ @already_updating = false
39
+
40
+ install_gems
41
+ end
42
+
43
+ def install_gems
44
+ command_runner.run!("bundle install --local") do |runner|
45
+ runner.retries = 5
46
+ end
47
+ end
48
+
49
+ def version_of(gem)
50
+ GemVersion.new(Bundler.definition.specs[gem][0].version)
51
+ end
52
+
53
+ def includes?(gem)
54
+ Bundler.definition.dependencies.any? do |dependency|
55
+ dependency.name == gem
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ attr_reader :fs, :command_runner
62
+
63
+ def already_updating?
64
+ @already_updating
65
+ end
66
+
67
+ def assemble_gem_line(gem, version, options)
68
+ formatted_options = options.
69
+ map { |key, value| "#{key}: #{formatted_value(value)}" }.
70
+ join(", ")
71
+
72
+ line = %(gem '#{gem}')
73
+
74
+ if version
75
+ line << %(, '#{version}')
76
+ end
77
+
78
+ if options.any?
79
+ line << %(, #{formatted_options})
80
+ end
81
+
82
+ line << "\n"
83
+ end
84
+
85
+ def formatted_value(value)
86
+ if value.is_a?(Pathname)
87
+ value.to_s.inspect
88
+ else
89
+ value.inspect
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,232 @@
1
+ require "forwardable"
2
+ require "timeout"
3
+ require "shellwords"
4
+
5
+ module Snowglobe
6
+ class CommandRunner
7
+ extend Forwardable
8
+
9
+ TimeoutError = Class.new(StandardError)
10
+
11
+ def self.run(*args, **options, &block)
12
+ new(*args, **options, &block).tap(&:call)
13
+ end
14
+
15
+ def self.run!(*args, **options, &block)
16
+ run(*args, run_successfully: true, **options, &block)
17
+ end
18
+
19
+ attr_reader :status, :options, :env
20
+ attr_accessor :command_prefix, :run_quickly, :run_successfully, :retries,
21
+ :timeout
22
+
23
+ def initialize(
24
+ *args,
25
+ env: {},
26
+ directory: Dir.pwd,
27
+ run_successfully: false,
28
+ **options
29
+ )
30
+ @reader, @writer = IO.pipe
31
+ @options = options.merge(err: [:child, :out], out: writer)
32
+
33
+ @args = args
34
+ @env = normalize_env(env)
35
+ self.directory = directory
36
+ @run_successfully = run_successfully
37
+
38
+ @wrapper = ->(block) { block.call }
39
+ @command_prefix = ""
40
+ @run_quickly = false
41
+ @retries = 1
42
+ @num_times_run = 0
43
+ @timeout = 20
44
+
45
+ yield self if block_given?
46
+ end
47
+
48
+ def directory
49
+ options[:chdir]
50
+ end
51
+
52
+ def directory=(directory)
53
+ if directory.nil?
54
+ raise ArgumentError, "Must provide a directory"
55
+ end
56
+
57
+ options[:chdir] = directory
58
+ end
59
+
60
+ def around_command(&block)
61
+ @wrapper = block
62
+ end
63
+
64
+ def formatted_command
65
+ [formatted_env, Shellwords.join(command)].
66
+ reject(&:empty?).
67
+ join(" ")
68
+ end
69
+
70
+ def call
71
+ possibly_retrying do
72
+ possibly_running_quickly do
73
+ run_with_debugging
74
+
75
+ if run_successfully && !success?
76
+ fail!
77
+ end
78
+ end
79
+ end
80
+
81
+ self
82
+ end
83
+
84
+ def stop
85
+ unless writer.closed?
86
+ writer.close
87
+ end
88
+ end
89
+
90
+ def output
91
+ @_output ||= begin
92
+ stop
93
+ without_colors(reader.read)
94
+ end
95
+ end
96
+
97
+ def elided_output
98
+ lines = output.split(/\n/)
99
+ new_lines = lines[0..4]
100
+
101
+ if lines.size > 10
102
+ new_lines << "(...#{lines.size - 10} more lines...)"
103
+ end
104
+
105
+ new_lines << lines[-5..-1]
106
+ new_lines.join("\n")
107
+ end
108
+
109
+ def_delegators :status, :success?
110
+
111
+ def exit_status
112
+ status.exitstatus
113
+ end
114
+
115
+ def fail!
116
+ raise <<-MESSAGE
117
+ Command #{formatted_command.inspect} exited with status #{exit_status}.
118
+ Output:
119
+ #{divider("START") + output + divider("END")}
120
+ MESSAGE
121
+ end
122
+
123
+ def has_output?(expected_output)
124
+ if expected_output.is_a?(Regexp)
125
+ output =~ expected_output
126
+ else
127
+ output.include?(expected_output)
128
+ end
129
+ end
130
+
131
+ protected
132
+
133
+ attr_reader :args, :reader, :writer, :wrapper
134
+
135
+ private
136
+
137
+ def normalize_env(env)
138
+ env.reduce({}) do |hash, (key, value)|
139
+ hash.merge(key.to_s => value)
140
+ end
141
+ end
142
+
143
+ def command
144
+ ([command_prefix] + args).flatten.flat_map do |word|
145
+ Shellwords.split(word)
146
+ end
147
+ end
148
+
149
+ def formatted_env
150
+ env.map { |key, value| "#{key}=#{value.inspect}" }.join(" ")
151
+ end
152
+
153
+ def run
154
+ pid = spawn(env, *command, options)
155
+ Process.waitpid(pid)
156
+ @status = $?
157
+ end
158
+
159
+ def run_with_wrapper
160
+ wrapper.call(method(:run))
161
+ end
162
+
163
+ def run_with_debugging
164
+ debug { "\n\e[33mChanging to directory:\e[0m #{directory}" }
165
+ debug { "\e[32mRunning command:\e[0m #{formatted_command}" }
166
+
167
+ run_with_wrapper
168
+
169
+ debug { "\n" + divider("START") + output + divider("END") }
170
+ end
171
+
172
+ def possibly_running_quickly(&block)
173
+ if run_quickly
174
+ begin
175
+ Timeout.timeout(timeout, &block)
176
+ rescue Timeout::Error
177
+ stop
178
+
179
+ message =
180
+ "Command timed out after #{timeout} seconds: " +
181
+ "#{formatted_command}\n" +
182
+ "Output:\n" +
183
+ output
184
+
185
+ raise TimeoutError, message
186
+ end
187
+ else
188
+ yield
189
+ end
190
+ end
191
+
192
+ def possibly_retrying
193
+ @num_times_run += 1
194
+ yield
195
+ rescue StandardError => error
196
+ debug { "#{error.class}: #{error.message}" }
197
+
198
+ if @num_times_run < @retries
199
+ sleep @num_times_run
200
+ retry
201
+ else
202
+ raise error
203
+ end
204
+ end
205
+
206
+ def divider(title = "")
207
+ total_length = 72
208
+ start_length = 3
209
+
210
+ string = ""
211
+ string << ("-" * start_length)
212
+ string << title
213
+ string << "-" * (total_length - start_length - title.length)
214
+ string << "\n"
215
+ string
216
+ end
217
+
218
+ def without_colors(string)
219
+ string.gsub(/\e\[\d+(?:;\d+)?m(.+?)\e\[0m/, '\1')
220
+ end
221
+
222
+ def debugging_enabled?
223
+ ENV["DEBUG_COMMANDS"] == "1"
224
+ end
225
+
226
+ def debug
227
+ if debugging_enabled?
228
+ puts yield
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,43 @@
1
+ module Snowglobe
2
+ class Configuration
3
+ attr_accessor :root_directory
4
+ attr_accessor :temporary_directory
5
+ attr_writer :project_name
6
+
7
+ def initialize
8
+ self.temporary_directory = Pathname.new("/tmp/snowglobe")
9
+ @project_name = nil
10
+ @database_name = nil
11
+ end
12
+
13
+ def project_name
14
+ if @project_name
15
+ "#{@project_name}-test-app"
16
+ else
17
+ raise NotConfiguredError.new(<<~EXAMPLE)
18
+ Snowglobe.configure do |config|
19
+ config.project_name = "your_project_name"
20
+ end
21
+ EXAMPLE
22
+ end
23
+ end
24
+
25
+ def database_name
26
+ @database_name || project_name
27
+ end
28
+
29
+ def update!
30
+ yield self
31
+ end
32
+
33
+ class NotConfiguredError < StandardError
34
+ def initialize(example)
35
+ super(<<~MESSAGE)
36
+ You need to configure Snowglobe before you can use it! For example:
37
+
38
+ #{example}
39
+ MESSAGE
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,28 @@
1
+ require_relative "database_configuration"
2
+
3
+ module Snowglobe
4
+ class Database
5
+ ADAPTER_NAME = ENV.fetch("DATABASE_ADAPTER", "sqlite3").to_sym
6
+
7
+ attr_reader :config
8
+
9
+ def initialize
10
+ @config = DatabaseConfiguration.for(
11
+ Snowglobe.configuration.database_name,
12
+ ADAPTER_NAME,
13
+ )
14
+ end
15
+
16
+ def name
17
+ config.database
18
+ end
19
+
20
+ def adapter_name
21
+ config.adapter
22
+ end
23
+
24
+ def adapter_class
25
+ config.adapter_class
26
+ end
27
+ end
28
+ end