heroku_hatchet 1.4.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c80bac9dc11b95cdcbfb5156c7b90c4975afbf82
4
- data.tar.gz: 7700ae73439de98a7043e1fdebdde5e80a80462e
3
+ metadata.gz: a9f803b82d8440304dca2283cdcb3f1f9d689094
4
+ data.tar.gz: bfd8d1b84f7fdd80f53535905f474bfdc82d5e59
5
5
  SHA512:
6
- metadata.gz: 131ba4fd7860bc306214a9e6f05136c67637380b9fac1ff0591e3a4584895b43c5aecec3d23c8cce28b2d3df2786e32e3f6a07f5f0a932205b169c444b10d466
7
- data.tar.gz: 48000503d40b2c7f2cacfcca0dc3b0c59baa501b61563be38172d0ea8d95776d0bbb24468cadfc9bc1fdf43cada27e4c43f111c163e6eb4ce8c7a6af05e1c0c5
6
+ metadata.gz: 7f7cfcda96ee5f0222afc0cb70b2119007a8efadd6f72dd389b25df6754a46c7bb4336e88591e6678066f681ae4381af3f124221fb3b3ebdff24ec1435d2176a
7
+ data.tar.gz: cf49871c076c769bf1bcd5f3cc24f4c1607de040ef684b3dd493fb5073dde7af70e3baff264406801aade89c423ce0378de8f7e43877af969798be29856abfa1
data/.travis.yml CHANGED
@@ -9,4 +9,4 @@ env:
9
9
  - HATCHET_BUILDPACK_BRANCH=master
10
10
  - HATCHET_RETRIES=3
11
11
  - HATCHET_APP_LIMIT=80
12
- - secure: IuwT8MLmcdWIZvuD+XgHE9jh/49NcZ5Q6Wh7M2ROIQPsNjQ9jPHFpTTdL6p0/63eq948WUKrrPqDFRm/jh/JKyJPmQfDgWq4ScoPdurhgLlGg1CH81gi22t9VwO5QeoYLufMyueqazGbQCAl+xsa+hsKcs6nb836yDHsmBud6z0=
12
+ - secure: TvpZ0CrIe0FqjyTUOAtVqjHHrtF1esMroa00bYYRBas050/y7ygVpAn9utFZZChgt1PUbM48I01UaQglGxtmXVl3ahQXtPpXlzlwJOlDS09dlZFfLenkv530/pxIlpRtqk4q18gCoLBblXX7RZu3TGt0qds+o8dQrBzL6QifAHs=
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## HEAD
2
2
 
3
+ ## 2.0.0
4
+
5
+ - Support for Heroku CI added
6
+ - Removed support for Anvil
7
+
3
8
  ## 1.4.1
4
9
 
5
10
  - Shell commands now escaped automatically
data/README.md CHANGED
@@ -38,13 +38,25 @@ To prevent regressions and to make pushing out new features faster and easier.
38
38
 
39
39
  ## What can Hatchet Test?
40
40
 
41
- Hatchet can easily test deployment of buildpacks, getting the build output, and running arbitrary interactive processes such as `heroku run bash`.
41
+ Hatchet can easily test deployment of buildpacks, getting the build output, and running arbitrary interactive processes such as `heroku run bash`. It can also test running CI against an app.
42
+
43
+ ## Writing Tests
44
+
45
+ Hatchet is test framework agnostic. [This project](https://github.com/heroku/hatchet) uses `Test::Unit` to run it's own tests. While the [heroku-ruby-buildpack](https://github.com/heroku/heroku-buildpack-ruby) uses rspec.
46
+
47
+ Rspec has a number of advantages, the ability to run `focused: true` to only run the exact test you want as well as the ability to tag tests. Rspec also has a number of useful plugins, one especialy useful one is `gem 'rspec-retry'` which will re-run any failed tests a given number of times (I recommend setting this to at least 2) this decrease the number of false negatives your tests will have.
48
+
49
+ Whatever testing framework you chose, we recommend using a parallel test runner when running the full suite [parallel_tests](https://github.com/grosser/parallel_tests) works with rspec and test::unit and is amazing.
50
+
51
+ If you're unfamiliar with the ruby testing eco-system or want some help, looking at existing projects is a good place to get started.
52
+
53
+ There is a section below on getting Hatchet to work on Travis.
42
54
 
43
55
  ## Testing a Buildpack
44
56
 
45
57
  Hatchet was built for testing the Ruby buildpack, but you can use it to test any buildpack you desire provided you don't mind writing your tests written in Ruby.
46
58
 
47
- You will need copies of applications that can be deployed by your buildpack. You can see the ones for the Hatchet unit tests (and the Ruby buildpack) https://github.com/sharpstone. Hatchet does not require that you keep these apps checked into your git repo which would make fetching your buildpack slow instead declare them in a `hatchet.json` file (see below).
59
+ You will need copies of applications that can be deployed by your buildpack. You can see the ones for the Hatchet unit tests (and the Ruby buildpack) https://github.com/sharpstone. Hatchet does not require that you keep these apps checked into your git repo which would make fetching your buildpack slow, instead declare them in a `hatchet.json` file (see below).
48
60
 
49
61
  Hatchet will automate retrieving these files `$ hatchet install`, as well as deploying them using your local copy of the buildpack, retrieving the build output and running commands against deploying applications.
50
62
 
@@ -100,38 +112,19 @@ spontaneously fail. In the future we may create a hatchet.lockfile or
100
112
  something to declare the commit
101
113
 
102
114
 
103
- ## Deployments: Anvil vs. Git
104
-
105
- Before you start testing a buildpack, understand that there are two different ways to deploy to Heroku. The first you are likely familiar with Git, requires a `git push heroku master`. You can configure the buildpack of an app being deployed in this way through the `BUILDPACK_URL` environment variable of the app. The buildpack url must be publicaly available.
106
-
107
- The second method is by [Anvil](https://github.com/ddollar/anvil-cli) . In this method the build is performed as a service. This service takes an app to be built as well as a buildpack. When using Anvil, you do not need to put your buildpack anywhere publicly available.
108
-
109
- When developing local features you will likely wish to use Anvil since it does not require a publicly available URL and you can iterate faster. When testing for regression you will almost always want to use Git, since it is the closest approximation to real world deployment. For this reason Hatchet provides a globally configurable way to toggle between the two deployment modes `Hatchet::Runner`
110
-
111
-
112
115
  ## Deploying apps
113
116
 
114
- Now that you've got your apps locally you can have hatchet deploy them for you. Hatchet can deploy using one of two ways Anvil and Git. To specify one or the other set your `HATCHET_DEPLOY_STRATEGY` environment variable to `anvil` or `git`. The default is `anvil`. In production, you should always test against `git`
115
-
116
-
117
- A `Hatchet::GitApp` will deploy using the standard `git push heroku master` and is not configurable, if you use this option you need to have a publicly accessible copy of your buildpack. Using Git to test your buildpack may be slow and require you to frequently push your buildpack to a public git repo. For this reason we recommend using Anvil to run your tests locally:
118
-
119
- ```ruby
120
- Hatchet::Runner.new("rails3_mri_193").deploy do |app|
121
-
122
- end
123
- ```
124
117
 
125
- If you are using GIT, you can specify the location of your public buildpack url in an environment variable:
118
+ You can specify the location of your public buildpack url in an environment variable:
126
119
 
127
120
  ```sh
128
121
  HATCHET_BUILDPACK_BASE=https://github.com/heroku/heroku-buildpack-ruby.git
129
122
  HATCHET_BUILDPACK_BRANCH=master
130
123
  ```
131
124
 
132
- If you do not specify `HATCHET_BUILDPACK_URL` the default Ruby buildpack will be used. If you do not specify a `HATCHET_BUILDPACK_BRANCH` the current branch you are on will be used.
125
+ If you do not specify `HATCHET_BUILDPACK_URL` the default Ruby buildpack will be used. If you do not specify a `HATCHET_BUILDPACK_BRANCH` the current branch you are on will be used. This is how the Ruby buildpack runs tests on branches on travis (by leaving `HATCHET_BUILDPACK_BRANCH` blank).
133
126
 
134
- Deploys are expected to work, if the `ENV['HATCHET_RETRIES']` is set, then deploys will be automatically retried that number of times. Due to testing using a network and random Anvil failures, setting this value to `3` retries seems to work well. If an app cannot be deployed within its allotted number of retries an error will be raised.
127
+ Deploys are expected to work, if the `ENV['HATCHET_RETRIES']` is set, then deploys will be automatically retried that number of times. Due to testing using a network and random failures, setting this value to `3` retries seems to work well. If an app cannot be deployed within its allotted number of retries an error will be raised.
135
128
 
136
129
  If you are testing an app that is supposed to fail deployment you can set the `allow_failure: true` flag when creating the app:
137
130
 
@@ -139,7 +132,7 @@ If you are testing an app that is supposed to fail deployment you can set the `a
139
132
  Hatchet::Runner.new("no_lockfile", allow_failure: true).deploy do |app|
140
133
  ```
141
134
 
142
- After the block finishes your app will be removed from heroku. If you are investigating a deploy, you can add the `debug: true` flag to your app:
135
+ After the block finishes your app will be queued to be removed from heroku. If you are investigating a deploy, you can add the `debug: true` flag to your app:
143
136
 
144
137
  ```ruby
145
138
  Hatchet::Runner.new("rails3_mri_193", debug: true).deploy do |app|
@@ -155,21 +148,15 @@ app = Hatchet::Runner.new("rails3_mri_193", name: "testapp")
155
148
 
156
149
  Deploying the app takes a few minutes, so you may want to skip that part to make debugging a problem easier since you're iterating much faster.
157
150
 
158
-
159
- If you need to deploy using a buildpack that is not in the root of your directory you can specify a path in the `buildpack` option:
160
-
151
+ If you need to deploy using a different buildpack you can specify one manually:
161
152
 
162
153
  ```ruby
163
- buildpack_path = File.expand_path 'test/fixtures/buildpacks/heroku-buildpack-ruby'
164
154
 
165
155
  def test_deploy
166
- Hatchet::GitApp.new("rails3_mri_193", buildpack: buildpack_path).deploy do |app|
156
+ Hatchet::Runner.new("rails3_mri_193", buildpack: "https://github.com/heroku/heroku-buildpack-ruby.git").deploy do |app|
167
157
  # ...
168
158
  ```
169
159
 
170
- If you are using a `Hatchet::GitApp` this is where you specify the publicly avaialble location of your buildpack, such as `https://github.com/heroku/heroku-buildpack-ruby.git#mybranch`
171
-
172
-
173
160
  ## Getting Deploy Output
174
161
 
175
162
  After Hatchet deploys your app you can get the output by using `app.output`
@@ -201,6 +188,15 @@ Hatchet::Runner.new("rails3_mri_193").deploy do |app|
201
188
 
202
189
  This is useful for checking the existence of generated files such as assets. If you need to run an interactive session such as `heroku run bash` or `heroku run rails console` you can use the run command and pass a block:
203
190
 
191
+ ```
192
+ Hatchet::Runner.new("rails3_mri_193").deploy do |app|
193
+ app.run("cat Procfile")
194
+ end
195
+ ```
196
+
197
+ This is the prefered way to run commands against the app. You can also string together commands in a session, however due to difficulties in driving a REPL programatically via [repl_runner](http://github.com/schneems/repl_runner) it's less deterministic.
198
+
199
+
204
200
  ```ruby
205
201
  Hatchet::Runner.new("rails3_mri_193").deploy do |app|
206
202
  app.run("bash") do |bash|
@@ -210,38 +206,56 @@ Hatchet::Runner.new("rails3_mri_193").deploy do |app|
210
206
  end
211
207
  ```
212
208
 
213
- or
209
+ Please read the docs on [repl_runner](http://github.com/schneems/repl_runner) for more info. The only interactive commands that are supported out of the box are `rails console`, `bash`, and `irb` it is fairly easy to add your own though:
210
+
211
+ ## Heroku CI
212
+
213
+ Hatchet supports testing Heroku CI.
214
214
 
215
215
  ```ruby
216
- Hatchet::Runner.new("rails3_mri_193").deploy do |app|
217
- app.run("rails console") do |console|
218
- console.run("a = 1 + 2") {|result| assert_match "3", result }
219
- console.run("'foo' * a") {|result| assert_match "foofoofoo", result }
220
- end
216
+ Hatchet::Runner.new("rails5_ruby_schema_format").run_ci do |test_run|
217
+ assert_match "Ruby buildpack tests completed successfully", test_run.output
221
218
  end
222
219
  ```
223
220
 
224
- This functionality is provided by [repl_runner](http://github.com/schneems/repl_runner). Please read the docs on that readme for more info. The only interactive commands that are supported out of the box are `rails console`, `bash`, and `irb` it is fairly easy to add your own though:
221
+ Call the `run_ci` method on the hatchet `Runner`. The object passed to the block is a `Hatchet::TestRun` object. You can call:
225
222
 
226
- ```
227
- ReplRunner.register_commands(:python) do |config|
228
- config.terminate_command "exit()" # the command you use to end the 'python' console
229
- config.startup_timeout 60 # seconds to boot
230
- config.return_char "\n" # the character that submits the command
231
- end
232
- ```
223
+ - `test_run.output` this will have the setup and test output of your tests.
224
+ - `test_run.app` this has a reference to the "app" you're testing against, however currently no `heroku create` is run (as it's not needed to run tests, only a pipeline and a blob of code).
233
225
 
234
- If you have questions on setting running other interactive commands message [@schneems](http://twitter.com/schneems)
226
+ An exception will be raised if either the test times out or a status of `:errored` or `:failed` is returned. If you expect your test to fail, you can pass in `allow_failure: true` when creating your hatchet runner. If you do that, you'll also get access to different statuses:
235
227
 
236
- ## Writing Tests
228
+ - `test_run.status` this will return a symbol of the status of your test. Statuses include, but are not limited to `:pending`, `:building`, `:errored`, `:creating`, `:succeeded`, and `:failed`
237
229
 
238
- Hatchet is test framework agnostic. [This project](https://github.com/heroku/hatchet) uses `Test::Unit` to run it's own tests. While the [heroku-ruby-buildpack](https://github.com/heroku/heroku-buildpack-ruby) uses rspec.
230
+ You can pass in a different timeout to the `run_ci` method `run_ci(timeout: 300)`.
239
231
 
240
- Rspec has a number of advantages, the ability to run `focused: true` to only run the exact test you want as well as the ability to tag tests. Rspec also has a number of useful plugins, one especialy useful one is `gem 'rspec-retry'` which will re-run any failed tests a given number of times (I recommend setting this to at least 2) this decrease the number of false negatives your tests will have.
232
+ You will likely need an `app.json` in the root directory of the app you're deploying. For example:
241
233
 
242
- Whatever testing framework you chose, we recommend using a parallel test runner when running the full suite [parallel_tests](https://github.com/grosser/parallel_tests) works with rspec and test::unit and is amazing.
234
+ ```json
235
+ {
236
+ "environments": {
237
+ "test": {
238
+ "addons":[
239
+ "heroku-postgresql"
240
+ ]
241
+ }
242
+ }
243
+ }
244
+ ```
245
+
246
+ This is on [a Rails5 test app](https://github.com/sharpstone/rails5_ruby_schema_format/blob/master/app.json) that needs the database to run.
243
247
 
244
- If you're unfamiliar with the ruby testing eco-system or want some help with boilerplate and work for Heroku: [@schneems](http://twitter.com/schneems) can help you get started. Looking at existing projects is a good place to get started
248
+ Do **NOT** specify a `buildpacks` key in the `app.json` as Hatchet will do this for you automatically. If you need to set buildpacks you can pass them into the `buildpacks:` keword argument:
249
+
250
+ ```
251
+ buildpacks = []
252
+ buildpacks << "https://github.com/heroku/heroku-buildpack-pgbouncer.git"
253
+ buildpacks << [HATCHET_BUILDPACK_BASE, HATCHET_BUILDPACK_BRANCH.call].join("#")
254
+
255
+ Hatchet::Runner.new("rails5_ruby_schema_format", buildpacks: buildpacks).run_ci do |test_run|
256
+ # ...
257
+ end
258
+ ```
245
259
 
246
260
  ## Testing on Travis
247
261
 
data/hatchet.gemspec CHANGED
@@ -18,6 +18,7 @@ Gem::Specification.new do |gem|
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
19
  gem.require_paths = ["lib"]
20
20
 
21
+ gem.add_dependency "platform-api", "~> 1"
21
22
  gem.add_dependency "heroku-api", "~> 0"
22
23
  gem.add_dependency "activesupport", "~> 4"
23
24
  gem.add_dependency "rrrretry", "~> 1"
data/hatchet.json CHANGED
@@ -3,5 +3,9 @@
3
3
  "rails3": ["sharpstone/rails3_mri_193"],
4
4
  "rails2": ["sharpstone/rails2blog"],
5
5
  "bundler": ["sharpstone/no_lockfile"],
6
- "default": ["sharpstone/default_ruby"]
6
+ "default": ["sharpstone/default_ruby"],
7
+ "ci": [
8
+ "sharpstone/rails5_ruby_schema_format",
9
+ "sharpstone/rails5_ci_fails_no_database"
10
+ ]
7
11
  }
data/lib/hatchet.rb CHANGED
@@ -9,39 +9,27 @@ require 'stringio'
9
9
  require 'fileutils'
10
10
  require 'stringio'
11
11
 
12
-
13
12
  module Hatchet
14
- RETRIES = Integer(ENV['HATCHET_RETRIES'] || 1)
13
+ end
15
14
 
16
- class App
17
- end
18
15
 
19
- def self.git_branch
20
- return ENV['TRAVIS_BRANCH'] if ENV['TRAVIS_BRANCH']
21
- out = `git describe --contains --all HEAD`.strip
22
- raise "Attempting to find current branch name. Error: Cannot describe git: #{out}" unless $?.success?
23
- out
24
- end
25
-
26
- def self.set_deploy_strategy!
27
- deploy_strat = (ENV['HATCHET_DEPLOY_STRATEGY'] || :git).to_sym
28
- case Hatchet::const_set("DEPLOY_STRATEGY", deploy_strat)
29
- when :anvil
30
- Hatchet.const_set("Runner", Hatchet::AnvilApp)
31
- when :git
32
- Hatchet.const_set("Runner", Hatchet::GitApp)
33
- else
34
- raise "unknown deploy strategy #{Hatchet::DEPLOY_STRATEGY}, expected 'anvil', 'git'"
35
- end
36
- end
37
- end
38
16
 
39
17
  require 'hatchet/version'
40
18
  require 'hatchet/reaper'
19
+ require 'hatchet/test_run'
41
20
  require 'hatchet/app'
42
21
  require 'hatchet/anvil_app'
43
22
  require 'hatchet/git_app'
44
23
  require 'hatchet/config'
45
24
 
25
+ module Hatchet
26
+ RETRIES = Integer(ENV['HATCHET_RETRIES'] || 1)
27
+ Runner = Hatchet::GitApp
46
28
 
47
- Hatchet.set_deploy_strategy!
29
+ def self.git_branch
30
+ return ENV['TRAVIS_BRANCH'] if ENV['TRAVIS_BRANCH']
31
+ out = `git describe --contains --all HEAD`.strip
32
+ raise "Attempting to find current branch name. Error: Cannot describe git: #{out}" unless $?.success?
33
+ out
34
+ end
35
+ end
@@ -3,65 +3,8 @@ require 'stringio'
3
3
 
4
4
  module Hatchet
5
5
  class AnvilApp < App
6
-
7
6
  def initialize(directory, options = {})
8
- @buildpack = options[:buildpack]
9
- @buildpack ||= File.expand_path('.')
10
- super
11
- end
12
-
13
- def push_without_retry!
14
- out, err = wrap_stdout_and_rescue(Anvil::Builder::BuildError) do
15
- slug_url = Anvil::Engine.build(".", :buildpack => @buildpack, :pipeline => true)
16
- puts "Releasing to http://#{@name}.herokuapp.com"
17
- response = release(@name, slug_url)
18
- while response.status == 202
19
- response = Excon.get("#{release_host}#{response.headers["Location"]}")
20
- end
21
- end
22
-
23
- err.string
24
- end
25
-
26
- def wrap_stdout_and_rescue(error, &block)
27
- wrap_stdout do |orig_out, orig_err|
28
- begin
29
- yield orig_out, orig_err
30
- rescue error => e
31
- return [$stdout.dup, $stderr.dup] if @allow_failure
32
- orig_out.puts $stderr.dup.string # print the errors to the test output
33
- raise e
34
- end
35
- end
36
- end
37
-
38
- def wrap_stdout(orig_out = $stdout, orig_err = $stderr, &block)
39
- $stderr = StringIO.new
40
- $stdout = StringIO.new
41
- yield orig_out, orig_err
42
- puts [$stdout.dup, $stderr.dup].inspect
43
- return $stdout.dup, $stderr.dup
44
- ensure
45
- $stdout = orig_out
46
- $stderr = orig_err
47
- end
48
-
49
- def teardown!
50
- super
51
- FileUtils.rm_rf("#{directory}/.anvil")
52
- end
53
-
54
- private
55
- def release(name, slug_url)
56
- headers = {"Content-Type" => "application/json", accept: :json}
57
- release_options = {description: "Anvil Build", slug_url: slug_url }
58
- Excon.post("#{release_host}/v1/apps/#{name}/release",
59
- headers: headers,
60
- body: release_options.to_json)
61
- end
62
-
63
- def release_host
64
- "https://:#{api_key}@cisaurus.heroku.com"
7
+ raise "Anvil no longer works"
65
8
  end
66
9
  end
67
10
  end
data/lib/hatchet/app.rb CHANGED
@@ -1,7 +1,12 @@
1
1
  require 'shellwords'
2
+ require 'platform-api'
2
3
 
3
4
  module Hatchet
4
5
  class App
6
+ HATCHET_BUILDPACK_BASE = (ENV['HATCHET_BUILDPACK_BASE'] || "https://github.com/heroku/heroku-buildpack-ruby.git")
7
+ HATCHET_BUILDPACK_BRANCH = -> { ENV['HATCHET_BUILDPACK_BRANCH'] || Hatchet.git_branch }
8
+ BUILDPACK_URL = "https://github.com/heroku/heroku-buildpack-ruby.git"
9
+
5
10
  attr_reader :name, :stack, :directory, :repo_name
6
11
 
7
12
  class FailedDeploy < StandardError
@@ -22,9 +27,14 @@ module Hatchet
22
27
  @debug = options[:debug] || options[:debugging]
23
28
  @allow_failure = options[:allow_failure] || false
24
29
  @labs = ([] << options[:labs]).flatten.compact
30
+ @buildpack = options[:buildpack] || options[:buildpack_url] || [HATCHET_BUILDPACK_BASE, HATCHET_BUILDPACK_BRANCH.call].join("#")
25
31
  @reaper = Reaper.new(heroku)
26
32
  end
27
33
 
34
+ def allow_failure?
35
+ @allow_failure
36
+ end
37
+
28
38
  # config is read only, should be threadsafe
29
39
  def self.config
30
40
  @config ||= Config.new
@@ -194,6 +204,58 @@ module Hatchet
194
204
  @heroku ||= Heroku::API.new(api_key: api_key)
195
205
  end
196
206
 
207
+ def run_ci(timeout: 300, &block)
208
+ Hatchet::RETRIES.times.retry do
209
+ result = create_pipeline
210
+ @pipeline_id = result["id"]
211
+ end
212
+
213
+ # create_app
214
+ # platform_api.pipeline_coupling.create(app: name, pipeline: @pipeline_id, stage: "development")
215
+ test_run = TestRun.new(token: api_key,
216
+ buildpacks: @buildpack,
217
+ timeout: timeout,
218
+ app: self,
219
+ pipeline: @pipeline_id)
220
+
221
+ Hatchet::RETRIES.times.retry do
222
+ test_run.create_test_run
223
+ end
224
+ test_run.wait!(&block)
225
+ ensure
226
+ delete_pipeline(@pipeline_id) if @pipeline_id
227
+ end
228
+
229
+ def pipeline_id
230
+ @pipeline_id
231
+ end
232
+
233
+ def create_pipeline
234
+ platform_api.pipeline.create(name: @name)
235
+ end
236
+
237
+ def source_get_url
238
+ create_source
239
+ @source_get_url
240
+ end
241
+
242
+ def create_source
243
+ @create_source ||= begin
244
+ result = platform_api.source.create
245
+ @source_get_url = result["source_blob"]["get_url"]
246
+ @source_put_url = result["source_blob"]["put_url"]
247
+ @source_put_url
248
+ end
249
+ end
250
+
251
+ def delete_pipeline(pipeline_id)
252
+ platform_api.pipeline.delete(pipeline_id)
253
+ end
254
+
255
+ def platform_api
256
+ @platform_api ||= PlatformAPI.connect_oauth(api_key)
257
+ end
258
+
197
259
  private
198
260
  # if someone uses bundle exec
199
261
  def bundle_exec
@@ -1,15 +1,6 @@
1
1
  module Hatchet
2
2
  # used for deploying a test app to heroku via git
3
3
  class GitApp < App
4
- HATCHET_BUILDPACK_BASE = (ENV['HATCHET_BUILDPACK_BASE'] || "https://github.com/heroku/heroku-buildpack-ruby.git")
5
- HATCHET_BUILDPACK_BRANCH = -> { ENV['HATCHET_BUILDPACK_BRANCH'] || Hatchet.git_branch }
6
- BUILDPACK_URL = "https://github.com/heroku/heroku-buildpack-ruby.git"
7
-
8
- def initialize(directory, options = {})
9
- @buildpack = options[:buildpack] || options[:buildpack_url] || [HATCHET_BUILDPACK_BASE, HATCHET_BUILDPACK_BRANCH.call].join("#")
10
- super
11
- end
12
-
13
4
  def setup!
14
5
  super
15
6
  heroku.put_config_vars(name, 'BUILDPACK_URL' => @buildpack)
@@ -25,7 +25,7 @@ module Hatchet
25
25
 
26
26
  def cycle(apps = get_apps)
27
27
  if over_limit?
28
- if @hatchet_apps.count > 0
28
+ if @hatchet_apps.count > 1
29
29
  destroy_oldest
30
30
  cycle
31
31
  else
@@ -0,0 +1,192 @@
1
+ module Hatchet
2
+ class FailedTestError < StandardError
3
+ def initialize(app, output)
4
+ msg = "Could run tests on pipeline id: '#{app.pipeline_id}' (#{app.repo_name}) at path: '#{app.directory}'\n" <<
5
+ " if this was expected add `allow_failure: true` to your hatchet initialization hash.\n" <<
6
+ "output:\n" <<
7
+ "#{output}"
8
+ super(msg)
9
+ end
10
+ end
11
+
12
+ class TestRun
13
+
14
+ # Hatchet::GitApp.new("rails3_mri_193").run_ci do |test_run|
15
+ # assert :succeeded, test_run.status
16
+ # end
17
+ #
18
+ # TestRun.new(token: , buildpacks: , test_dir: )
19
+ #
20
+ def initialize(
21
+ token:,
22
+ buildpacks:,
23
+ app:,
24
+ pipeline:,
25
+ timeout: 10,
26
+ pause: 5,
27
+ commit_sha: "sha",
28
+ commit_branch: "master",
29
+ commit_message: "commit",
30
+ organization: nil
31
+ )
32
+ @pipeline = pipeline || "hatchet-t-#{SecureRandom.hex(10)}"
33
+ @timeout = timeout
34
+ @pause = pause
35
+ @organization = organization
36
+ @token = token
37
+ @commit_sha = commit_sha
38
+ @commit_branch = commit_branch
39
+ @commit_message = commit_message
40
+ @buildpacks = Array(buildpacks)
41
+ @app = app
42
+ @mutex = Mutex.new
43
+ @status = false
44
+ end
45
+ attr_reader :app
46
+
47
+ def create_test_run
48
+ @mutex.synchronize do
49
+ raise "Test is already running" if @status
50
+ @status = :building
51
+
52
+ body = {
53
+ source_blob_url: source_blob_url,
54
+ pipeline: @pipeline,
55
+ organization: @organization,
56
+ commit_sha: @commit_sha,
57
+ commit_branch: @commit_branch,
58
+ commit_message: @commit_message,
59
+ }
60
+
61
+ # https://github.com/heroku/api/blob/master/schema/variants/3.ci/platform-api-reference.md#test-run-create
62
+ attributes = excon_request(
63
+ method: :post,
64
+ path: "/test-runs",
65
+ version: "3.ci",
66
+ body: body,
67
+ expects: [201]
68
+ )
69
+ @test_run_id = attributes["id"]
70
+ end
71
+ info
72
+ end
73
+
74
+ def info
75
+ # GET /test-runs/{test_run_id}
76
+ response = excon_request(
77
+ method: :get,
78
+ path: "/test-runs/#{@test_run_id}",
79
+ version: "3.ci",
80
+ expects: [201, 200]
81
+ )
82
+
83
+ @setup_stream_url = response["setup_stream_url"]
84
+ @output_stream_url = response["output_stream_url"]
85
+
86
+ @status = response["status"].to_sym
87
+ end
88
+
89
+ def status
90
+ @status # :pending, :building, :creating, :succeeded, :failed, :errored
91
+ end
92
+
93
+ def output
94
+ get_contents_or_whatever(@setup_stream_url) +
95
+ get_contents_or_whatever(@output_stream_url)
96
+ end
97
+
98
+ def wait!
99
+ Timeout::timeout(@timeout) do
100
+ while true do
101
+ info
102
+ case @status
103
+ when :succeeded
104
+ yield self
105
+ return self
106
+ when :failed, :errored
107
+ raise FailedTestError.new(self.app, self.output) unless app.allow_failure?
108
+ yield self
109
+ return self
110
+ else
111
+ # keep looping
112
+ end
113
+ sleep @pause
114
+ end
115
+ end
116
+ rescue Timeout::Error
117
+ puts "Timed out status: #{@status}, timeout: #{@timeout}"
118
+ raise FailedTestError.new(self.app, self.output) unless app.allow_failure?
119
+ yield self
120
+ return self
121
+ end
122
+
123
+ # Here's where the magic happens folks
124
+ #
125
+ # == Set the buildpack
126
+ #
127
+ # We take the current directory structure and see if it has an `app.json`
128
+ # This is how Heroku CI knows what buildpacks to use to run your tests
129
+ # Hatchet will inject whatever buildpack you pass to it, by default
130
+ # it uses the same buildpack you have specified in your Hatchet constants
131
+ # and uses the same branch your tests are using
132
+ #
133
+ # == Generate source blob url
134
+ #
135
+ # The CI endpoint takes a url that has a `.tgz` file to execute your tests.
136
+ # We pull down the app you're testing against, inject an `app.json` (or modify
137
+ # if it already exists). We the use the heroku "source" api to generate a
138
+ # url that we can put our newly generated `.tgz` file. It also returns a "get"
139
+ # url where those contents can be downloaded. We pass this url back to CI
140
+ #
141
+ def source_blob_url
142
+ @app.in_directory do
143
+ app_json = JSON.parse(File.read("app.json")) if File.exist?("app.json")
144
+ app_json ||= {}
145
+ app_json["environments"] ||= {}
146
+ app_json["environments"]["test"] ||= {}
147
+ app_json["environments"]["test"]["buildpacks"] = @buildpacks.map {|b| { url: b } }
148
+ File.open("app.json", "w") {|f| f.write(JSON.generate(app_json)) }
149
+
150
+ `tar c . | gzip -9 > slug.tgz`
151
+
152
+ source_put_url = @app.create_source
153
+ Hatchet::RETRIES.times.retry do
154
+ Excon.put(source_put_url,
155
+ expects: [200],
156
+ body: File.read('slug.tgz'))
157
+ end
158
+ end
159
+ return @app.source_get_url
160
+ end
161
+
162
+ private
163
+ def get_contents_or_whatever(url)
164
+ Excon.get(url, read_timeout: @pause).body
165
+ rescue Excon::Error::Timeout
166
+ ""
167
+ end
168
+
169
+ def excon_request(options)
170
+ JSON.parse(raw_excon_request(options).body)
171
+ end
172
+
173
+ def version
174
+ "3"
175
+ end
176
+
177
+ def raw_excon_request(options)
178
+ version = options[:version] || 3
179
+ options[:headers] = {
180
+ "Authorization" => "Bearer #{@token}",
181
+ "Accept" => "application/vnd.heroku+json; version=#{version}",
182
+ "Content-Type" => "application/json"
183
+ }.merge(options[:headers] || {})
184
+ options[:body] = JSON.generate(options[:body]) if options[:body]
185
+
186
+ Hatchet::RETRIES.times.retry do
187
+ connection = Excon.new("https://api.heroku.com")
188
+ return connection.request(options)
189
+ end
190
+ end
191
+ end
192
+ end
@@ -1,3 +1,3 @@
1
1
  module Hatchet
2
- VERSION = "1.4.3"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -0,0 +1,24 @@
1
+ require 'test_helper'
2
+
3
+
4
+ class CITest < Test::Unit::TestCase
5
+
6
+ def test_ci_create_app_with_stack
7
+ Hatchet::GitApp.new("rails5_ruby_schema_format").run_ci do |test_run|
8
+ assert_match "Ruby buildpack tests completed successfully", test_run.output
9
+ assert_equal :succeeded, test_run.status
10
+ end
11
+ end
12
+
13
+ def test_error_with_bad_app
14
+ error = assert_raise(Hatchet::FailedTestError) do
15
+ Hatchet::GitApp.new("rails5_ci_fails_no_database").run_ci {}
16
+ end
17
+
18
+ assert_match "PG::ConnectionBad: could not connect to server" ,error.message
19
+
20
+ Hatchet::GitApp.new("rails5_ci_fails_no_database", allow_failure: true).run_ci do |test_run|
21
+ assert_equal :errored, test_run.status
22
+ end
23
+ end
24
+ end
@@ -11,20 +11,26 @@ class ConfigTest < Test::Unit::TestCase
11
11
  end
12
12
 
13
13
  def test_config_dirs
14
- expected_dirs = {"test/fixtures/repos/bundler/no_lockfile" => "git://github.com/sharpstone/no_lockfile.git",
15
- "test/fixtures/repos/default/default_ruby" => "git://github.com/sharpstone/default_ruby.git",
16
- "test/fixtures/repos/default/default_ruby" => "git://github.com/sharpstone/default_ruby.git",
17
- "test/fixtures/repos/rails2/rails2blog" => "git://github.com/sharpstone/rails2blog.git",
18
- "test/fixtures/repos/rails3/rails3_mri_193" => "git://github.com/sharpstone/rails3_mri_193.git"}
19
- assert_equal expected_dirs, @config.dirs
20
- end
14
+ {
15
+ "test/fixtures/repos/bundler/no_lockfile" => "git://github.com/sharpstone/no_lockfile.git",
16
+ "test/fixtures/repos/default/default_ruby" => "git://github.com/sharpstone/default_ruby.git",
17
+ "test/fixtures/repos/default/default_ruby" => "git://github.com/sharpstone/default_ruby.git",
18
+ "test/fixtures/repos/rails2/rails2blog" => "git://github.com/sharpstone/rails2blog.git",
19
+ "test/fixtures/repos/rails3/rails3_mri_193" => "git://github.com/sharpstone/rails3_mri_193.git"
20
+ }.each do |key, value|
21
+ assert_include(key, value, @config.dirs)
22
+ end
23
+ end
21
24
 
22
25
  def test_config_repos
23
- expected_repos = {"default_ruby" => "test/fixtures/repos/default/default_ruby",
24
- "no_lockfile" => "test/fixtures/repos/bundler/no_lockfile",
25
- "rails2blog" => "test/fixtures/repos/rails2/rails2blog",
26
- "rails3_mri_193" => "test/fixtures/repos/rails3/rails3_mri_193"}
27
- assert_equal expected_repos, @config.repos
26
+ {
27
+ "default_ruby" => "test/fixtures/repos/default/default_ruby",
28
+ "no_lockfile" => "test/fixtures/repos/bundler/no_lockfile",
29
+ "rails2blog" => "test/fixtures/repos/rails2/rails2blog",
30
+ "rails3_mri_193" => "test/fixtures/repos/rails3/rails3_mri_193"
31
+ }.each do |key, value|
32
+ assert_include(key, value, @config.repos)
33
+ end
28
34
  end
29
35
 
30
36
  def test_no_internal_config_raises_no_errors
@@ -37,5 +43,10 @@ class ConfigTest < Test::Unit::TestCase
37
43
  @config.send :init_config!, {"foo" => ["schneems/sextant"]}
38
44
  assert_equal("git://github.com/schneems/sextant.git", @config.dirs["./repos/foo/sextant"])
39
45
  end
46
+ private
47
+
48
+ def assert_include(key, value, actual)
49
+ assert_equal value, actual[key], "Expected #{actual.inspect} to include #{ {key => value } } but it did not"
50
+ end
40
51
  end
41
52
 
data/test/test_helper.rb CHANGED
@@ -1,11 +1,11 @@
1
- Bundler.require
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'hatchet'
2
3
 
4
+ Bundler.require
3
5
 
4
- require 'hatchet'
5
6
  require 'test/unit'
6
7
  require "mocha/setup"
7
8
 
8
-
9
9
  def assert_tests_run
10
10
  end
11
11
 
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heroku_hatchet
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.3
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Schneeman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-11-01 00:00:00.000000000 Z
11
+ date: 2017-03-30 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: platform-api
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: heroku-api
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -231,6 +245,7 @@ files:
231
245
  - lib/hatchet/git_app.rb
232
246
  - lib/hatchet/reaper.rb
233
247
  - lib/hatchet/tasks.rb
248
+ - lib/hatchet/test_run.rb
234
249
  - lib/hatchet/version.rb
235
250
  - test/fixtures/buildpacks/null-buildpack/bin/compile
236
251
  - test/fixtures/buildpacks/null-buildpack/bin/detect
@@ -239,8 +254,8 @@ files:
239
254
  - test/fixtures/buildpacks/null-buildpack/readme.md
240
255
  - test/hatchet/allow_failure_anvil_test.rb
241
256
  - test/hatchet/allow_failure_git_test.rb
242
- - test/hatchet/anvil_test.rb
243
257
  - test/hatchet/app_test.rb
258
+ - test/hatchet/ci_test.rb
244
259
  - test/hatchet/config_test.rb
245
260
  - test/hatchet/edit_repo_test.rb
246
261
  - test/hatchet/git_test.rb
@@ -248,7 +263,6 @@ files:
248
263
  - test/hatchet/labs_test.rb
249
264
  - test/hatchet/multi_cmd_runner_test.rb
250
265
  - test/hatchet/reaper_test.rb
251
- - test/hatchet/runner_test.rb
252
266
  - test/test_helper.rb
253
267
  homepage: https://github.com/heroku/hatchet
254
268
  licenses:
@@ -270,7 +284,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
270
284
  version: '0'
271
285
  requirements: []
272
286
  rubyforge_project:
273
- rubygems_version: 2.5.1
287
+ rubygems_version: 2.6.11
274
288
  signing_key:
275
289
  specification_version: 4
276
290
  summary: Hatchet is a an integration testing library for developing Heroku buildpacks.
@@ -282,8 +296,8 @@ test_files:
282
296
  - test/fixtures/buildpacks/null-buildpack/readme.md
283
297
  - test/hatchet/allow_failure_anvil_test.rb
284
298
  - test/hatchet/allow_failure_git_test.rb
285
- - test/hatchet/anvil_test.rb
286
299
  - test/hatchet/app_test.rb
300
+ - test/hatchet/ci_test.rb
287
301
  - test/hatchet/config_test.rb
288
302
  - test/hatchet/edit_repo_test.rb
289
303
  - test/hatchet/git_test.rb
@@ -291,5 +305,4 @@ test_files:
291
305
  - test/hatchet/labs_test.rb
292
306
  - test/hatchet/multi_cmd_runner_test.rb
293
307
  - test/hatchet/reaper_test.rb
294
- - test/hatchet/runner_test.rb
295
308
  - test/test_helper.rb
@@ -1,7 +0,0 @@
1
- require 'test_helper'
2
-
3
- class AnvilTest < Test::Unit::TestCase
4
- def setup
5
- @buildpack_path = File.expand_path 'test/fixtures/buildpacks/heroku-buildpack-ruby'
6
- end
7
- end
@@ -1,31 +0,0 @@
1
- require 'test_helper'
2
-
3
- class HatchetRunnerTest < Test::Unit::TestCase
4
-
5
- def setup
6
- @default = ENV['HATCHET_DEPLOY_STRATEGY']
7
- end
8
-
9
- def teardown
10
- ENV['HATCHET_DEPLOY_STRATEGY'] = @default
11
- Hatchet.set_deploy_strategy!
12
- end
13
-
14
- def test_defaults
15
- assert_equal nil, ENV['HATCHET_DEPLOY_STRATEGY']
16
- assert_equal :git, Hatchet::DEPLOY_STRATEGY
17
- assert_equal Hatchet::GitApp, Hatchet::Runner
18
- end
19
-
20
- def test_change_deploy_strat
21
- ENV['HATCHET_DEPLOY_STRATEGY'] = "git"
22
- Hatchet.set_deploy_strategy!
23
- assert_equal :git, Hatchet::DEPLOY_STRATEGY
24
- assert_equal Hatchet::GitApp, Hatchet::Runner
25
-
26
- ENV['HATCHET_DEPLOY_STRATEGY'] = "anvil"
27
- Hatchet.set_deploy_strategy!
28
- assert_equal :anvil, Hatchet::DEPLOY_STRATEGY
29
- assert_equal Hatchet::AnvilApp, Hatchet::Runner
30
- end
31
- end