heroku_hatchet 0.2.0 → 1.0.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 +4 -4
- data/CHANGELOG.md +5 -1
- data/README.md +173 -102
- data/hatchet.gemspec +2 -0
- data/lib/hatchet/app.rb +10 -8
- data/lib/hatchet/version.rb +1 -1
- data/lib/hatchet.rb +1 -4
- data/test/hatchet/anvil_test.rb +2 -2
- data/test/hatchet/git_test.rb +1 -1
- data/test/hatchet/multi_cmd_runner_test.rb +13 -5
- metadata +16 -12
- data/lib/hatchet/command_parser.rb +0 -39
- data/lib/hatchet/process_spawn.rb +0 -67
- data/lib/hatchet/repl_runner.rb +0 -61
- data/lib/hatchet/stream_exec.rb +0 -62
- data/test/hatchet/command_parser_test.rb +0 -54
- data/test/hatchet/repl_runner_test.rb +0 -20
- data/test/hatchet/stream_exec_test.rb +0 -12
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: cbe186a27b0018f41b0ab9338925c09acdf9a52b
         | 
| 4 | 
            +
              data.tar.gz: b7dcdd7fdf884f4e6773e6ae5c34fc731b615876
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 66ae54f148dfde98f77515fedc1401aee42820084de8846d0dae3c1a350f0b3e19393033ecac347e14096b945adc5c7f84d3f0c26d1e72cbde78fbfd0b3b114a
         | 
| 7 | 
            +
              data.tar.gz: 8f2fc16b6a0aaa668eed1efd53b1807bbbceb15afc4a298bf8a081086bcb53c7e2afe3bb1539d605d943368ff7926d46dfa4678e36b292366e0f06f3949e41c2
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,5 +1,9 @@ | |
| 1 1 | 
             
            ## HEAD
         | 
| 2 2 |  | 
| 3 | 
            +
            ## 1.0.0
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            - Move remote console running code to https://github.com/schneems/repl_runner
         | 
| 6 | 
            +
              This changes the API for running interactive code. README has been updated
         | 
| 3 7 |  | 
| 4 8 | 
             
            ## 0.2.0
         | 
| 5 9 |  | 
| @@ -26,4 +30,4 @@ | |
| 26 30 |  | 
| 27 31 | 
             
            ## 0.0.1
         | 
| 28 32 |  | 
| 29 | 
            -
            - Initial Release
         | 
| 33 | 
            +
            - Initial Release
         | 
    
        data/README.md
    CHANGED
    
    | @@ -32,160 +32,231 @@ it will be pulled automatically via shelling out, but this is slower. | |
| 32 32 |  | 
| 33 33 | 
             
                $ bundle exec rake test
         | 
| 34 34 |  | 
| 35 | 
            +
            ## Why Test a Buildpack?
         | 
| 35 36 |  | 
| 36 | 
            -
             | 
| 37 | 
            +
            To prevent regressions and to make pushing out new features faster and easier.
         | 
| 37 38 |  | 
| 38 | 
            -
             | 
| 39 | 
            +
            ## What can Hatchet Test?
         | 
| 39 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 42 |  | 
| 42 | 
            -
             | 
| 43 | 
            +
            ## Testing a Buildpack
         | 
| 43 44 |  | 
| 44 | 
            -
             | 
| 45 | 
            -
                  ##
         | 
| 46 | 
            -
                end
         | 
| 45 | 
            +
            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.
         | 
| 47 46 |  | 
| 48 | 
            -
             | 
| 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).
         | 
| 49 48 |  | 
| 50 | 
            -
             | 
| 49 | 
            +
            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.
         | 
| 51 50 |  | 
| 52 | 
            -
                Hatchet::App.new("repos/rails3/codetriage").deploy do |app|
         | 
| 53 | 
            -
                  assert app.deployed?
         | 
| 54 | 
            -
                end
         | 
| 55 51 |  | 
| 56 | 
            -
             | 
| 52 | 
            +
            ## Hatchet.json
         | 
| 57 53 |  | 
| 58 | 
            -
             | 
| 59 | 
            -
                  app.run("bash") do |cmd|
         | 
| 60 | 
            -
                    assert cmd.run("ls public/assets").include?("application.css")
         | 
| 61 | 
            -
                  end
         | 
| 62 | 
            -
                end
         | 
| 54 | 
            +
            Hatchet expects a json file in the root of your buildpack called `hatchet.json`. You can configure install options using the `"hatchet"` key. In this example we're telling hatchet to install the given repos to our `test/fixtures` directory instead of the default current directory.
         | 
| 63 55 |  | 
| 64 | 
            -
             | 
| 56 | 
            +
            ```
         | 
| 57 | 
            +
            {
         | 
| 58 | 
            +
              "hatchet": {"directory": "test/fixtures"},
         | 
| 59 | 
            +
              "rails3":  ["sharpstone/rails3_mri_193"],
         | 
| 60 | 
            +
              "rails2":  ["sharpstone/rails2blog"],
         | 
| 61 | 
            +
              "bundler": ["sharpstone/no_lockfile"]
         | 
| 62 | 
            +
            }
         | 
| 63 | 
            +
            ```
         | 
| 65 64 |  | 
| 66 | 
            -
             | 
| 67 | 
            -
                cmd.run("cd")
         | 
| 68 | 
            -
                cmd.run("cd .. ; ls | grep foo")
         | 
| 65 | 
            +
            When you run `$ hatchet install` it will grab the git repos from github and place them on your local machine in a file structure that looks like this:
         | 
| 69 66 |  | 
| 70 | 
            -
             | 
| 67 | 
            +
            ```
         | 
| 68 | 
            +
            test/
         | 
| 69 | 
            +
              fixtures/
         | 
| 70 | 
            +
                repos/
         | 
| 71 | 
            +
                  rails3/
         | 
| 72 | 
            +
                    rails3_mri_193/
         | 
| 73 | 
            +
                  rails2/
         | 
| 74 | 
            +
                    rails2blog/
         | 
| 75 | 
            +
                  bundler/
         | 
| 76 | 
            +
                    no_lockfile/
         | 
| 77 | 
            +
            ```
         | 
| 71 78 |  | 
| 72 | 
            -
             | 
| 79 | 
            +
            Now in your test you can reference one of these applications by using it's git name:
         | 
| 73 80 |  | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 81 | 
            +
            ```ruby
         | 
| 82 | 
            +
            Hatchet::AnvilApp.new('no_lockfile')
         | 
| 83 | 
            +
            ```
         | 
| 77 84 |  | 
| 78 | 
            -
             | 
| 85 | 
            +
            If you have conflicting names, use full paths.
         | 
| 79 86 |  | 
| 87 | 
            +
            A word of warning on including repos inside of your test
         | 
| 88 | 
            +
            directory, if you're using a runner that looks for patterns such as
         | 
| 89 | 
            +
            `*_test.rb` to run your hatchet tests, it may incorrectly think you want
         | 
| 90 | 
            +
            to run the tests inside of the repos. To get rid of this
         | 
| 91 | 
            +
            problem move your repos direcory out of `test/` or be more specific
         | 
| 92 | 
            +
            with your tests such as moving them to a `test/hatchet` directory and
         | 
| 93 | 
            +
            changing your pattern if you are using `Rake::TestTask` it might look like this:
         | 
| 80 94 |  | 
| 81 | 
            -
             | 
| 95 | 
            +
                t.pattern = 'test/hatchet/**/*_test.rb'
         | 
| 82 96 |  | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 97 | 
            +
            A note on external repos: since you're basing tests on these repos, it
         | 
| 98 | 
            +
            is in your best interest to not change them or your tests may
         | 
| 99 | 
            +
            spontaneously fail. In the future we may create a hatchet.lockfile or
         | 
| 100 | 
            +
            something to declare the commit
         | 
| 85 101 |  | 
| 86 | 
            -
             | 
| 87 | 
            -
                  assert_match "1.9.3", app.run("ruby -v")
         | 
| 88 | 
            -
                end
         | 
| 102 | 
            +
            ## Deploying apps
         | 
| 89 103 |  | 
| 104 | 
            +
            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. A `Hatchet::AnvilApp` will deploy using Anvil against the current directory. This means that the buildpack you have locally will be used when deploying, due to this we recommend using Anvil to run your tests.
         | 
| 90 105 |  | 
| 91 | 
            -
             | 
| 106 | 
            +
            A `Hatchet::GitApp` will deploy using the standard `git push heroku master`, 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:
         | 
| 92 107 |  | 
| 93 | 
            -
             | 
| 108 | 
            +
            ```ruby
         | 
| 109 | 
            +
            Hatchet::AnvilApp.new("rails3_mri_193").deploy do |app|
         | 
| 94 110 |  | 
| 95 | 
            -
             | 
| 111 | 
            +
            end
         | 
| 112 | 
            +
            ```
         | 
| 96 113 |  | 
| 97 | 
            -
             | 
| 114 | 
            +
            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.
         | 
| 98 115 |  | 
| 99 | 
            -
             | 
| 100 | 
            -
            to deploy to Heroku. Web application repos, especially Rails repos, aren't known for
         | 
| 101 | 
            -
            being small, if you're testing a custom buildpack and have
         | 
| 102 | 
            -
            `BUILDPACK_URL` set in your app config, it needs to be cloned each time
         | 
| 103 | 
            -
            you deploy your app. If you've `git add`-ed a bunch of repos then this
         | 
| 104 | 
            -
            clone would be pretty slow, we're not going to do this. Do not commit
         | 
| 105 | 
            -
            your repos to git.
         | 
| 116 | 
            +
            If you are testing an app that is supposed to fail deployment you can set the `allow_failure: true` flag when creating the app:
         | 
| 106 117 |  | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 109 | 
            -
             | 
| 110 | 
            -
            git url. We will use it to sync remote git repos with your local
         | 
| 111 | 
            -
            project. It might look something like this
         | 
| 118 | 
            +
            ```ruby
         | 
| 119 | 
            +
            Hatchet::AnvilApp.new("no_lockfile", allow_failure: true).deploy do |app|
         | 
| 120 | 
            +
            ```
         | 
| 112 121 |  | 
| 113 | 
            -
             | 
| 114 | 
            -
                  "hatchet": {},
         | 
| 115 | 
            -
                  "rails3": ["git@github.com:codetriage/codetriage.git"],
         | 
| 116 | 
            -
                  "rails2": ["git@github.com:heroku/rails2blog.git"]
         | 
| 117 | 
            -
                }
         | 
| 122 | 
            +
            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:
         | 
| 118 123 |  | 
| 119 | 
            -
             | 
| 120 | 
            -
            .  | 
| 121 | 
            -
             | 
| 124 | 
            +
            ```ruby
         | 
| 125 | 
            +
            Hatchet::AnvilApp.new("rails3_mri_193", debug: true).deploy do |app|
         | 
| 126 | 
            +
            ```
         | 
| 122 127 |  | 
| 123 | 
            -
             | 
| 128 | 
            +
            Now after Hatchet is done deploying your app it will remain on Heroku. It will also output the name of the app into your test logs so that you can `heroku run bash` into it for detailed postmortem.
         | 
| 124 129 |  | 
| 125 | 
            -
             | 
| 130 | 
            +
            If you are wanting to run a test against a specific app without deploying to it, you can set the app name like this:
         | 
| 126 131 |  | 
| 127 | 
            -
             | 
| 128 | 
            -
             | 
| 129 | 
            -
             | 
| 130 | 
            -
                      #...
         | 
| 131 | 
            -
                  rails2/
         | 
| 132 | 
            -
                    rails2blog/
         | 
| 133 | 
            -
                      # ...
         | 
| 132 | 
            +
            ```ruby
         | 
| 133 | 
            +
            app = Hatchet::AnvilApp.new("rails3_mri_193", name: "testapp")
         | 
| 134 | 
            +
            ```
         | 
| 134 135 |  | 
| 135 | 
            -
             | 
| 136 | 
            -
            synced locally Hatchet will raise an error. Since you're using a
         | 
| 137 | 
            -
            standard file for your repos, you can now reference the name of the git
         | 
| 138 | 
            -
            repo, provided you don't have conflicting names:
         | 
| 136 | 
            +
            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.
         | 
| 139 137 |  | 
| 140 | 
            -
                Hatchet::App.new("codetriage").deploy do |app|
         | 
| 141 138 |  | 
| 142 | 
            -
            If you  | 
| 139 | 
            +
            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:
         | 
| 143 140 |  | 
| 144 | 
            -
            A word of warning on including rails/ruby repos inside of your test
         | 
| 145 | 
            -
            directory, if you're using a runner that looks for patterns such as
         | 
| 146 | 
            -
            `*_test.rb` to run your hatchet tests, it may incorrectly think you want
         | 
| 147 | 
            -
            to run the tests inside of the rails repositories. To get rid of this
         | 
| 148 | 
            -
            problem move your repos direcory out of `test/` or be more specific
         | 
| 149 | 
            -
            with your tests such as moving them to a `test/hatchet` directory and
         | 
| 150 | 
            -
            changing your pattern if you are using `Rake::TestTask` it might look like this:
         | 
| 151 141 |  | 
| 152 | 
            -
             | 
| 142 | 
            +
            ```ruby
         | 
| 143 | 
            +
            buildpack_path = File.expand_path 'test/fixtures/buildpacks/heroku-buildpack-ruby'
         | 
| 153 144 |  | 
| 154 | 
            -
             | 
| 155 | 
            -
             | 
| 156 | 
            -
             | 
| 157 | 
            -
             | 
| 145 | 
            +
            def test_deploy
         | 
| 146 | 
            +
              Hatchet::AnvilApp.new("rails3_mri_193", buildpack: buildpack_path).deploy do |app|
         | 
| 147 | 
            +
              # ...
         | 
| 148 | 
            +
            ```
         | 
| 158 149 |  | 
| 159 | 
            -
             | 
| 150 | 
            +
            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`
         | 
| 160 151 |  | 
| 161 | 
            -
            Hatchet has a CLI for installing and maintaining external repos you're
         | 
| 162 | 
            -
            using to test against. If you have Hatchet installed as a gem run
         | 
| 163 152 |  | 
| 164 | 
            -
             | 
| 153 | 
            +
            ## Getting Deploy Output
         | 
| 165 154 |  | 
| 166 | 
            -
             | 
| 167 | 
            -
            the command by going to the source code directory and running:
         | 
| 155 | 
            +
            After Hatchet deploys your app you can get the output by using `app.output`
         | 
| 168 156 |  | 
| 169 | 
            -
             | 
| 157 | 
            +
            ```ruby
         | 
| 158 | 
            +
            Hatchet::AnvilApp.new("rails3_mri_193").deploy do |app|
         | 
| 159 | 
            +
              puts app.output
         | 
| 160 | 
            +
            end
         | 
| 161 | 
            +
            ```
         | 
| 162 | 
            +
             | 
| 163 | 
            +
            If you told Hatchet to `allow_failure: true` then the full output of the failed build will be in `app.output` even though the app was not deployed. It is a good idea to test against the output for text that should be present. Using a testing framework such as `Test::Unit` a failed test output may look like this
         | 
| 164 | 
            +
             | 
| 165 | 
            +
            ```ruby
         | 
| 166 | 
            +
            Hatchet::AnvilApp.new("no_lockfile", allow_failure: true).deploy do |app|
         | 
| 167 | 
            +
              assert_match "Gemfile.lock required", app.output
         | 
| 168 | 
            +
            end
         | 
| 169 | 
            +
            ```
         | 
| 170 | 
            +
             | 
| 171 | 
            +
            Since an error will be raised on failed deploys you don't need to check for a deployed status (the error will automatically fail the test for you).
         | 
| 172 | 
            +
             | 
| 173 | 
            +
            ## Running Processes
         | 
| 174 | 
            +
             | 
| 175 | 
            +
            Often times asserting output of a build can only get you so far, and you will need to actually run a task on the dyno. To run a non-interactive command such as `heroku run ls` you can do this using the `app.run()` command and do not pass it a block
         | 
| 176 | 
            +
             | 
| 177 | 
            +
            ```ruby
         | 
| 178 | 
            +
            Hatchet::AnvilApp.new("rails3_mri_193").deploy do |app|
         | 
| 179 | 
            +
              assert_match "applications.css", app.run("ls public/assets")
         | 
| 180 | 
            +
            ```
         | 
| 181 | 
            +
             | 
| 182 | 
            +
            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:
         | 
| 183 | 
            +
             | 
| 184 | 
            +
            ```ruby
         | 
| 185 | 
            +
            Hatchet::AnvilApp.new("rails3_mri_193").deploy do |app|
         | 
| 186 | 
            +
              app.run("bash") do |bash|
         | 
| 187 | 
            +
                bash.run("ls")           {|result| assert_match "Gemfile.lock", result }
         | 
| 188 | 
            +
                bash.run("cat Procfile") {|result| assert_match "web:", result }
         | 
| 189 | 
            +
              end
         | 
| 190 | 
            +
            end
         | 
| 191 | 
            +
            ```
         | 
| 192 | 
            +
             | 
| 193 | 
            +
            or
         | 
| 194 | 
            +
             | 
| 195 | 
            +
            ```ruby
         | 
| 196 | 
            +
            Hatchet::AnvilApp.new("rails3_mri_193").deploy do |app|
         | 
| 197 | 
            +
              app.run("rails console") do |console|
         | 
| 198 | 
            +
                console.run("a = 1 + 2")  {|result| assert_match "3", result }
         | 
| 199 | 
            +
                console.run("'foo' * a")  {|result| assert_match "foofoofoo", result }
         | 
| 200 | 
            +
              end
         | 
| 201 | 
            +
            end
         | 
| 202 | 
            +
            ```
         | 
| 203 | 
            +
             | 
| 204 | 
            +
            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:
         | 
| 205 | 
            +
             | 
| 206 | 
            +
            ```
         | 
| 207 | 
            +
            ReplRunner.register_commands(:python)  do |config|
         | 
| 208 | 
            +
              config.terminate_command "exit()"        # the command you use to end the 'python' console
         | 
| 209 | 
            +
              config.startup_timeout 60                # seconds to boot
         | 
| 210 | 
            +
              config.return_char "\n"                  # the character that submits the command
         | 
| 211 | 
            +
            end
         | 
| 212 | 
            +
            ```
         | 
| 213 | 
            +
             | 
| 214 | 
            +
            If you have questions on setting running other interactive commands message [@schneems](http://twitter.com/schneems)
         | 
| 215 | 
            +
             | 
| 216 | 
            +
            ## Writing Tests
         | 
| 170 217 |  | 
| 218 | 
            +
            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.
         | 
| 171 219 |  | 
| 172 | 
            -
             | 
| 220 | 
            +
            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.
         | 
| 173 221 |  | 
| 174 | 
            -
             | 
| 222 | 
            +
            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.
         | 
| 175 223 |  | 
| 176 | 
            -
             | 
| 224 | 
            +
            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
         | 
| 177 225 |  | 
| 178 | 
            -
             | 
| 226 | 
            +
            ## Testing on Travis
         | 
| 179 227 |  | 
| 180 | 
            -
             | 
| 228 | 
            +
            Once you've got your tests working locally, you'll likely want to get them running on Travis because a) CI is awesome, and b) you can use pull requests to run your all your tests in parallel without having to kill your network connection.
         | 
| 181 229 |  | 
| 182 | 
            -
             | 
| 230 | 
            +
            To run on travis you will need to configure your `.travis.yml` to run the appropriate commands and to set up encrypted data so you can run tests against a valid heroku user.
         | 
| 183 231 |  | 
| 184 | 
            -
             | 
| 232 | 
            +
            For reference see the `.travis.yml` from [hatchet](https://github.com/heroku/hatchet/blob/master/.travis.yml) and the [heroku-ruby-buildpack](https://github.com/heroku/heroku-buildpack-ruby/blob/master/.travis.yml). To make running on travis easier there is a rake task in Hatchet that can be run before your tests are executed
         | 
| 233 | 
            +
             | 
| 234 | 
            +
            ```
         | 
| 235 | 
            +
            before_script: bundle exec rake hatchet:setup_travis
         | 
| 236 | 
            +
            ```
         | 
| 237 | 
            +
             | 
| 238 | 
            +
            I recommend signing up for a new heroku account for running your tests on travis, otherwise you will quickly excede your API limit. Once you have the new api token you can use this technique to [securely send travis the data](http://about.travis-ci.org/docs/user/build-configuration/#Secure-environment-variables).
         | 
| 239 | 
            +
             | 
| 240 | 
            +
             | 
| 241 | 
            +
            ## Extra App Commands
         | 
| 242 | 
            +
             | 
| 243 | 
            +
            ```
         | 
| 244 | 
            +
            app.add_database # adds a database to specified app
         | 
| 245 | 
            +
            app.heroku       # returns a Herou Api client https://github.com/heroku/heroku.rb
         | 
| 246 | 
            +
            ```
         | 
| 247 | 
            +
             | 
| 248 | 
            +
            ## Hatchet CLI
         | 
| 249 | 
            +
             | 
| 250 | 
            +
            Hatchet has a CLI for installing and maintaining external repos you're
         | 
| 251 | 
            +
            using to test against. If you have Hatchet installed as a gem run
         | 
| 252 | 
            +
             | 
| 253 | 
            +
                $ hatchet --help
         | 
| 254 | 
            +
             | 
| 255 | 
            +
            For more info on commands. If you're using the source code you can run
         | 
| 256 | 
            +
            the command by going to the source code directory and running:
         | 
| 257 | 
            +
             | 
| 258 | 
            +
                $ ./bin/hatchet --help
         | 
| 185 259 |  | 
| 186 | 
            -
            It would be great to allow hatchet to deploy apps off of git url, however if we do that we could open ourselves up to false negatives, if we are pointing at an external repo that gets broken.
         | 
| 187 260 |  | 
| 188 261 |  | 
| 189 | 
            -
            ## Features?
         | 
| 190 262 |  | 
| 191 | 
            -
            What else do we want to test? Config vars, addons, etc. Let's write some tests.
         | 
    
        data/hatchet.gemspec
    CHANGED
    
    
    
        data/lib/hatchet/app.rb
    CHANGED
    
    | @@ -39,7 +39,10 @@ module Hatchet | |
| 39 39 | 
             
                # runs a command on heroku similar to `$ heroku run #foo`
         | 
| 40 40 | 
             
                # but programatically and with more control
         | 
| 41 41 | 
             
                def run(command, timeout = nil, &block)
         | 
| 42 | 
            -
                   | 
| 42 | 
            +
                  heroku_command = "heroku run #{command} -a #{name}"
         | 
| 43 | 
            +
                  return `#{heroku_command}` if block.blank?
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  ReplRunner.new(command, heroku_command, startup_timeout: timeout).run(&block)
         | 
| 43 46 | 
             
                end
         | 
| 44 47 |  | 
| 45 48 | 
             
                # set debug: true when creating app if you don't want it to be
         | 
| @@ -115,14 +118,13 @@ module Hatchet | |
| 115 118 | 
             
                  @output
         | 
| 116 119 | 
             
                end
         | 
| 117 120 |  | 
| 118 | 
            -
                 | 
| 119 | 
            -
                   | 
| 120 | 
            -
             | 
| 121 | 
            -
                  end
         | 
| 121 | 
            +
                def api_key
         | 
| 122 | 
            +
                  @api_key ||= ENV['HEROKU_API_KEY'] || `heroku auth:token`.chomp
         | 
| 123 | 
            +
                end
         | 
| 122 124 |  | 
| 123 | 
            -
             | 
| 124 | 
            -
             | 
| 125 | 
            -
             | 
| 125 | 
            +
                def heroku
         | 
| 126 | 
            +
                  @heroku ||= Heroku::API.new(api_key: api_key)
         | 
| 127 | 
            +
                end
         | 
| 126 128 | 
             
              end
         | 
| 127 129 | 
             
            end
         | 
| 128 130 |  | 
    
        data/lib/hatchet/version.rb
    CHANGED
    
    
    
        data/lib/hatchet.rb
    CHANGED
    
    | @@ -2,6 +2,7 @@ require 'heroku/api' | |
| 2 2 | 
             
            require 'anvil/engine'
         | 
| 3 3 | 
             
            require 'active_support/core_ext/object/blank'
         | 
| 4 4 | 
             
            require 'rrrretry'
         | 
| 5 | 
            +
            require 'repl_runner'
         | 
| 5 6 |  | 
| 6 7 | 
             
            require 'json'
         | 
| 7 8 | 
             
            require 'stringio'
         | 
| @@ -20,8 +21,4 @@ require 'hatchet/version' | |
| 20 21 | 
             
            require 'hatchet/app'
         | 
| 21 22 | 
             
            require 'hatchet/anvil_app'
         | 
| 22 23 | 
             
            require 'hatchet/git_app'
         | 
| 23 | 
            -
            require 'hatchet/command_parser'
         | 
| 24 | 
            -
            require 'hatchet/stream_exec'
         | 
| 25 | 
            -
            require 'hatchet/repl_runner'
         | 
| 26 | 
            -
            require 'hatchet/process_spawn'
         | 
| 27 24 | 
             
            require 'hatchet/config'
         | 
    
        data/test/hatchet/anvil_test.rb
    CHANGED
    
    | @@ -11,8 +11,8 @@ class AnvilTest < Test::Unit::TestCase | |
| 11 11 |  | 
| 12 12 | 
             
                  assert_match '1.9.3', app.run("ruby -v")
         | 
| 13 13 | 
             
                  app.run("bash") do |cmd|
         | 
| 14 | 
            -
                     | 
| 15 | 
            -
                     | 
| 14 | 
            +
                    cmd.run("cat Gemfile")      {|r| assert_match "gem 'pg'", r}
         | 
| 15 | 
            +
                    cmd.run("ls public/assets") {|r| assert_match "application.css", r}
         | 
| 16 16 | 
             
                  end
         | 
| 17 17 | 
             
                end
         | 
| 18 18 | 
             
              end
         | 
    
        data/test/hatchet/git_test.rb
    CHANGED
    
    
| @@ -11,18 +11,26 @@ class MultiCmdRunnerTest < Test::Unit::TestCase | |
| 11 11 | 
             
                Hatchet::AnvilApp.new("rails3_mri_193", buildpack: @buildpack_path).deploy do |app|
         | 
| 12 12 | 
             
                  app.add_database
         | 
| 13 13 |  | 
| 14 | 
            -
                   | 
| 15 | 
            -
                    app.run(" | 
| 16 | 
            -
                       | 
| 14 | 
            +
                  assert_raise ReplRunner::UnregisteredCommand do
         | 
| 15 | 
            +
                    app.run("ls", 2) do |ls| # will return right away, should raise error
         | 
| 16 | 
            +
                      ls.run("cat")
         | 
| 17 17 | 
             
                    end
         | 
| 18 18 | 
             
                  end
         | 
| 19 19 |  | 
| 20 20 | 
             
                  rand(3..7).times do
         | 
| 21 21 | 
             
                    app.run("rails console") do |console|
         | 
| 22 | 
            -
                       | 
| 23 | 
            -
                       | 
| 22 | 
            +
                      console.run("`ls`")
         | 
| 23 | 
            +
                      console.run("'foo' * 5")          {|r| assert_match "foofoofoofoofoo", r }
         | 
| 24 | 
            +
                      console.run("'hello ' + 'world'") {|r| assert_match "hello world", r }
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  rand(3..7).times do
         | 
| 29 | 
            +
                    app.run("bash") do |bash|
         | 
| 30 | 
            +
                      bash.run("ls") { |r| assert_match "Gemfile", r }
         | 
| 24 31 | 
             
                    end
         | 
| 25 32 | 
             
                  end
         | 
| 33 | 
            +
             | 
| 26 34 | 
             
                end
         | 
| 27 35 | 
             
              end
         | 
| 28 36 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: heroku_hatchet
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 1.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: 2013-06- | 
| 11 | 
            +
            date: 2013-06-26 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: heroku-api
         | 
| @@ -94,6 +94,20 @@ dependencies: | |
| 94 94 | 
             
                - - '>='
         | 
| 95 95 | 
             
                  - !ruby/object:Gem::Version
         | 
| 96 96 | 
             
                    version: '0'
         | 
| 97 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 98 | 
            +
              name: repl_runner
         | 
| 99 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 100 | 
            +
                requirements:
         | 
| 101 | 
            +
                - - '>='
         | 
| 102 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 103 | 
            +
                    version: '0'
         | 
| 104 | 
            +
              type: :runtime
         | 
| 105 | 
            +
              prerelease: false
         | 
| 106 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 107 | 
            +
                requirements:
         | 
| 108 | 
            +
                - - '>='
         | 
| 109 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 110 | 
            +
                    version: '0'
         | 
| 97 111 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 98 112 | 
             
              name: rake
         | 
| 99 113 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -157,12 +171,8 @@ files: | |
| 157 171 | 
             
            - lib/hatchet.rb
         | 
| 158 172 | 
             
            - lib/hatchet/anvil_app.rb
         | 
| 159 173 | 
             
            - lib/hatchet/app.rb
         | 
| 160 | 
            -
            - lib/hatchet/command_parser.rb
         | 
| 161 174 | 
             
            - lib/hatchet/config.rb
         | 
| 162 175 | 
             
            - lib/hatchet/git_app.rb
         | 
| 163 | 
            -
            - lib/hatchet/process_spawn.rb
         | 
| 164 | 
            -
            - lib/hatchet/repl_runner.rb
         | 
| 165 | 
            -
            - lib/hatchet/stream_exec.rb
         | 
| 166 176 | 
             
            - lib/hatchet/tasks.rb
         | 
| 167 177 | 
             
            - lib/hatchet/version.rb
         | 
| 168 178 | 
             
            - test/fixtures/buildpacks/heroku-buildpack-ruby/.gitignore
         | 
| @@ -204,12 +214,9 @@ files: | |
| 204 214 | 
             
            - test/hatchet/allow_failure_anvil_test.rb
         | 
| 205 215 | 
             
            - test/hatchet/allow_failure_git_test.rb
         | 
| 206 216 | 
             
            - test/hatchet/anvil_test.rb
         | 
| 207 | 
            -
            - test/hatchet/command_parser_test.rb
         | 
| 208 217 | 
             
            - test/hatchet/config_test.rb
         | 
| 209 218 | 
             
            - test/hatchet/git_test.rb
         | 
| 210 219 | 
             
            - test/hatchet/multi_cmd_runner_test.rb
         | 
| 211 | 
            -
            - test/hatchet/repl_runner_test.rb
         | 
| 212 | 
            -
            - test/hatchet/stream_exec_test.rb
         | 
| 213 220 | 
             
            - test/test_helper.rb
         | 
| 214 221 | 
             
            homepage: https://github.com/heroku/hatchet
         | 
| 215 222 | 
             
            licenses:
         | 
| @@ -275,10 +282,7 @@ test_files: | |
| 275 282 | 
             
            - test/hatchet/allow_failure_anvil_test.rb
         | 
| 276 283 | 
             
            - test/hatchet/allow_failure_git_test.rb
         | 
| 277 284 | 
             
            - test/hatchet/anvil_test.rb
         | 
| 278 | 
            -
            - test/hatchet/command_parser_test.rb
         | 
| 279 285 | 
             
            - test/hatchet/config_test.rb
         | 
| 280 286 | 
             
            - test/hatchet/git_test.rb
         | 
| 281 287 | 
             
            - test/hatchet/multi_cmd_runner_test.rb
         | 
| 282 | 
            -
            - test/hatchet/repl_runner_test.rb
         | 
| 283 | 
            -
            - test/hatchet/stream_exec_test.rb
         | 
| 284 288 | 
             
            - test/test_helper.rb
         | 
| @@ -1,39 +0,0 @@ | |
| 1 | 
            -
            # removes the commands from strings retrieved from stuff like `heroku run bash`
         | 
| 2 | 
            -
            # since likely you care about the output, not the input
         | 
| 3 | 
            -
            # this is especially useful for seeing if a given input command has finished running
         | 
| 4 | 
            -
            # if we cannot find a valid input command and output command return the full unparsed string
         | 
| 5 | 
            -
            module Hatchet
         | 
| 6 | 
            -
              class CommandParser
         | 
| 7 | 
            -
                attr_accessor :command
         | 
| 8 | 
            -
             | 
| 9 | 
            -
                def initialize(command)
         | 
| 10 | 
            -
                  @command = command
         | 
| 11 | 
            -
                  @parsed_string = ""
         | 
| 12 | 
            -
                  @raw_string    = ""
         | 
| 13 | 
            -
                end
         | 
| 14 | 
            -
             | 
| 15 | 
            -
                def regex
         | 
| 16 | 
            -
                  /#{Regexp.quote(command)}\r*\n+/
         | 
| 17 | 
            -
                end
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                def parse(string)
         | 
| 20 | 
            -
                  @raw_string    = string
         | 
| 21 | 
            -
                  @parsed_string = string.split(regex).last
         | 
| 22 | 
            -
                  return self
         | 
| 23 | 
            -
                end
         | 
| 24 | 
            -
             | 
| 25 | 
            -
                def to_s
         | 
| 26 | 
            -
                  @parsed_string || @raw_string
         | 
| 27 | 
            -
                end
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                def missing_valid_output?
         | 
| 30 | 
            -
                  !has_valid_output?
         | 
| 31 | 
            -
                end
         | 
| 32 | 
            -
             | 
| 33 | 
            -
                def has_valid_output?
         | 
| 34 | 
            -
                  return false unless @raw_string.match(regex)
         | 
| 35 | 
            -
                  return false if @parsed_string.blank? || @parsed_string.strip.blank?
         | 
| 36 | 
            -
                  true
         | 
| 37 | 
            -
                end
         | 
| 38 | 
            -
              end
         | 
| 39 | 
            -
            end
         | 
| @@ -1,67 +0,0 @@ | |
| 1 | 
            -
            require 'pty'
         | 
| 2 | 
            -
            module Hatchet
         | 
| 3 | 
            -
              # spawns a process on Heroku, and keeps it open for writing
         | 
| 4 | 
            -
              # like `heroku run bash`
         | 
| 5 | 
            -
              class ProcessSpawn
         | 
| 6 | 
            -
                attr_reader :command, :app, :timeout, :pid
         | 
| 7 | 
            -
                TIMEOUT = 60 # seconds to bring up a heroku command like `heroku run bash`
         | 
| 8 | 
            -
             | 
| 9 | 
            -
                def initialize(command, app, timeout = nil)
         | 
| 10 | 
            -
                  raise "need command" unless command.present?
         | 
| 11 | 
            -
                  raise "need app"     unless app.present?
         | 
| 12 | 
            -
                  @command        = "heroku run #{command} -a #{app.name}"
         | 
| 13 | 
            -
                  @ready_regex    = "^run.*up.*#{command}"
         | 
| 14 | 
            -
                  @app            = app
         | 
| 15 | 
            -
                  @timeout        = timeout || TIMEOUT
         | 
| 16 | 
            -
                end
         | 
| 17 | 
            -
             | 
| 18 | 
            -
                def ready?
         | 
| 19 | 
            -
                  @ready ||= `heroku ps -a #{app.name}`.match(/#{@ready_regex}/).present?
         | 
| 20 | 
            -
                end
         | 
| 21 | 
            -
             | 
| 22 | 
            -
                def not_ready?
         | 
| 23 | 
            -
                  !ready?
         | 
| 24 | 
            -
                end
         | 
| 25 | 
            -
             | 
| 26 | 
            -
                def wait_for_spawn!
         | 
| 27 | 
            -
                  while not_ready?
         | 
| 28 | 
            -
                    sleep 1
         | 
| 29 | 
            -
                  end
         | 
| 30 | 
            -
                  return true
         | 
| 31 | 
            -
                end
         | 
| 32 | 
            -
             | 
| 33 | 
            -
                # some REPL's don't sync standard out by default
         | 
| 34 | 
            -
                # try to do it auto-magically
         | 
| 35 | 
            -
                def repl_magic(repl)
         | 
| 36 | 
            -
                  case command
         | 
| 37 | 
            -
                  when /rails\s*console/, /\sirb\s/
         | 
| 38 | 
            -
                    # puts "magic for: '#{command}'"
         | 
| 39 | 
            -
                    repl.run("STDOUT.sync = true")
         | 
| 40 | 
            -
                  end
         | 
| 41 | 
            -
                end
         | 
| 42 | 
            -
             | 
| 43 | 
            -
                # Open up PTY (pseudo terminal) to command like `heroku run bash`
         | 
| 44 | 
            -
                # Wait for the dyno to deploy, then allow user to run arbitrary commands
         | 
| 45 | 
            -
                def spawn_repl
         | 
| 46 | 
            -
                  output, input, pid = PTY.spawn(command)
         | 
| 47 | 
            -
                  stream = StreamExec.new(output, input, pid)
         | 
| 48 | 
            -
                  repl   = ReplRunner.new(stream)
         | 
| 49 | 
            -
                  stream.timeout("waiting for spawn", timeout) do
         | 
| 50 | 
            -
                    wait_for_spawn!
         | 
| 51 | 
            -
                  end
         | 
| 52 | 
            -
                  raise "Could not run: '#{command}', command took longer than #{timeout} seconds" unless self.ready?
         | 
| 53 | 
            -
             | 
| 54 | 
            -
                  repl_magic(repl)
         | 
| 55 | 
            -
                  repl.wait_for_boot(5) # important to get rid of startup info i.e. "booting rails console ..."
         | 
| 56 | 
            -
                  return repl
         | 
| 57 | 
            -
                end
         | 
| 58 | 
            -
             | 
| 59 | 
            -
                def run(&block)
         | 
| 60 | 
            -
                  return `#{command}` if block.blank? # one off command, no block given
         | 
| 61 | 
            -
             | 
| 62 | 
            -
                  yield repl = spawn_repl
         | 
| 63 | 
            -
                ensure
         | 
| 64 | 
            -
                  repl.close if repl.present?
         | 
| 65 | 
            -
                end
         | 
| 66 | 
            -
              end
         | 
| 67 | 
            -
            end
         | 
    
        data/lib/hatchet/repl_runner.rb
    DELETED
    
    | @@ -1,61 +0,0 @@ | |
| 1 | 
            -
            # takes a StringExec class and attempts to parse commands out of it
         | 
| 2 | 
            -
            module Hatchet
         | 
| 3 | 
            -
              class ReplRunner
         | 
| 4 | 
            -
                TIMEOUT = 1
         | 
| 5 | 
            -
                RETRIES = 10
         | 
| 6 | 
            -
             | 
| 7 | 
            -
                attr_accessor :repl
         | 
| 8 | 
            -
             | 
| 9 | 
            -
                def initialize(repl, command_parser_klass = CommandParser)
         | 
| 10 | 
            -
                  @repl                 = repl
         | 
| 11 | 
            -
                  @command_parser_klass = command_parser_klass
         | 
| 12 | 
            -
                end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
                def command_parser_klass
         | 
| 15 | 
            -
                  @command_parser_klass
         | 
| 16 | 
            -
                end
         | 
| 17 | 
            -
             | 
| 18 | 
            -
                # adds a newline cause thats what most repl-s need to run command
         | 
| 19 | 
            -
                def write(cmd)
         | 
| 20 | 
            -
                  repl.write("#{cmd}\n")
         | 
| 21 | 
            -
                end
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                def run(cmd, options = {})
         | 
| 24 | 
            -
                  timeout = options[:timeout] || TIMEOUT
         | 
| 25 | 
            -
                  retries = options[:retries] || RETRIES
         | 
| 26 | 
            -
             | 
| 27 | 
            -
                  write(cmd)
         | 
| 28 | 
            -
                  read(cmd, timeout, retries)
         | 
| 29 | 
            -
                end
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                def wait_for_boot(timeout = 5)
         | 
| 32 | 
            -
                  repl.read(timeout)
         | 
| 33 | 
            -
                end
         | 
| 34 | 
            -
             | 
| 35 | 
            -
                def close
         | 
| 36 | 
            -
                  repl.close
         | 
| 37 | 
            -
                end
         | 
| 38 | 
            -
             | 
| 39 | 
            -
                # take in a command like "ls", and tries to find it in the output
         | 
| 40 | 
            -
                # of the repl (StreamExec)
         | 
| 41 | 
            -
                # Example
         | 
| 42 | 
            -
                #  output, input, pid = PTY.spawn('sh')
         | 
| 43 | 
            -
                #  stream             = StreamExec.new(output, input, pid)
         | 
| 44 | 
            -
                #  repl_runner = ReplRunner.new(stream)
         | 
| 45 | 
            -
                #  repl_runner.write("ls\n")
         | 
| 46 | 
            -
                #  repl_runner.read
         | 
| 47 | 
            -
                #     # => "app\tconfig.ru  Gemfile\t LICENSE.txt  public\t script  vendor\r\r\nbin\tdb\t   Gemfile.lock  log\t      Rakefile\t test\r\r\nconfig\tdoc\t   lib\t\t Procfile     README.md  tmp\r\r\n"
         | 
| 48 | 
            -
                #
         | 
| 49 | 
            -
                # if the command "ls" is not found, repl runner will continue to retry grabbing more output
         | 
| 50 | 
            -
                def read(cmd, timeout = TIMEOUT, retries = RETRIES)
         | 
| 51 | 
            -
                  str = ""
         | 
| 52 | 
            -
                  command_parser = command_parser_klass.new(cmd)
         | 
| 53 | 
            -
                  retries.times.each do
         | 
| 54 | 
            -
                    next if command_parser.has_valid_output?
         | 
| 55 | 
            -
                    str << repl.read(timeout)
         | 
| 56 | 
            -
                    command_parser.parse(str)
         | 
| 57 | 
            -
                  end
         | 
| 58 | 
            -
                  return command_parser.to_s
         | 
| 59 | 
            -
                end
         | 
| 60 | 
            -
              end
         | 
| 61 | 
            -
            end
         | 
    
        data/lib/hatchet/stream_exec.rb
    DELETED
    
    | @@ -1,62 +0,0 @@ | |
| 1 | 
            -
            require 'timeout'
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            module Hatchet
         | 
| 4 | 
            -
              # runs arbitrary commands within a Heroku process
         | 
| 5 | 
            -
              class StreamExec
         | 
| 6 | 
            -
                attr_reader :input, :output, :pid
         | 
| 7 | 
            -
                TIMEOUT = 1 # seconds to run an arbitrary command on a heroku process like `$ls`
         | 
| 8 | 
            -
             | 
| 9 | 
            -
                def initialize(output, input, pid)
         | 
| 10 | 
            -
                  @input  = input
         | 
| 11 | 
            -
                  @output = output
         | 
| 12 | 
            -
                  @pid    = pid
         | 
| 13 | 
            -
                end
         | 
| 14 | 
            -
             | 
| 15 | 
            -
                def write(cmd)
         | 
| 16 | 
            -
                  input.write(cmd)
         | 
| 17 | 
            -
                rescue Errno::EIO => e
         | 
| 18 | 
            -
                  raise e, "#{e.message} | trying to write '#{cmd}'"
         | 
| 19 | 
            -
                end
         | 
| 20 | 
            -
             | 
| 21 | 
            -
                def run(cmd, timeout = TIMEOUT)
         | 
| 22 | 
            -
                  write(cmd)
         | 
| 23 | 
            -
                  return read(timeout)
         | 
| 24 | 
            -
                end
         | 
| 25 | 
            -
             | 
| 26 | 
            -
                def close
         | 
| 27 | 
            -
                  timeout("closing stream") do
         | 
| 28 | 
            -
                    input.close
         | 
| 29 | 
            -
                    output.close
         | 
| 30 | 
            -
                  end
         | 
| 31 | 
            -
                ensure
         | 
| 32 | 
            -
                  Process.kill('TERM', pid)   if pid.present?
         | 
| 33 | 
            -
                end
         | 
| 34 | 
            -
             | 
| 35 | 
            -
                # There be dragons - (You're playing with process deadlock)
         | 
| 36 | 
            -
                #
         | 
| 37 | 
            -
                # We want to read the whole output of the command
         | 
| 38 | 
            -
                # First pull all contents from stdout (except we don't know how many there are)
         | 
| 39 | 
            -
                # So we have to go until our process deadlocks, then we timeout and return the string
         | 
| 40 | 
            -
                #
         | 
| 41 | 
            -
                def read(timeout = TIMEOUT)
         | 
| 42 | 
            -
                  str = ""
         | 
| 43 | 
            -
                  while true
         | 
| 44 | 
            -
                    Timeout::timeout(timeout) do
         | 
| 45 | 
            -
                      str << output.readline
         | 
| 46 | 
            -
                    end
         | 
| 47 | 
            -
                  end
         | 
| 48 | 
            -
             | 
| 49 | 
            -
                  return str
         | 
| 50 | 
            -
                rescue Timeout::Error, EOFError
         | 
| 51 | 
            -
                  return str
         | 
| 52 | 
            -
                end
         | 
| 53 | 
            -
             | 
| 54 | 
            -
                def timeout(msg = nil, val = TIMEOUT, &block)
         | 
| 55 | 
            -
                  Timeout::timeout(val) do
         | 
| 56 | 
            -
                    yield
         | 
| 57 | 
            -
                  end
         | 
| 58 | 
            -
                rescue Timeout::Error
         | 
| 59 | 
            -
                  puts "timeout #{msg}" if msg
         | 
| 60 | 
            -
                end
         | 
| 61 | 
            -
              end
         | 
| 62 | 
            -
            end
         | 
| @@ -1,54 +0,0 @@ | |
| 1 | 
            -
            require 'test_helper'
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            class CommandParserTest < Test::Unit::TestCase
         | 
| 4 | 
            -
              def test_removes_command_from_string
         | 
| 5 | 
            -
                hash = {command: "1+1",
         | 
| 6 | 
            -
                        string:  "1+1\r\r\n=> 2\r\r\n",
         | 
| 7 | 
            -
                        expect:   "=> 2\r\r\n"
         | 
| 8 | 
            -
                       }
         | 
| 9 | 
            -
                cp = Hatchet::CommandParser.new(hash[:command]).parse(hash[:string])
         | 
| 10 | 
            -
                assert cp.has_valid_output?
         | 
| 11 | 
            -
                assert_equal hash[:expect], cp.to_s
         | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
                hash = {command: "ls",
         | 
| 15 | 
            -
                        string: "Running `bash` attached to terminal... up, run.8041\r\n\e[01;34m~\e[00m \e[01;32m$ \e[00mls\r\r\napp  config\tdb   Gemfile\t   lib\tProcfile  Rakefile     script  tmp\r\r\nbin  config.ru\tdoc  Gemfile.lock  log\tpublic\t  README.rdoc  test    vendor\r\r\n",
         | 
| 16 | 
            -
                        expect: "app  config\tdb   Gemfile\t   lib\tProcfile  Rakefile     script  tmp\r\r\nbin  config.ru\tdoc  Gemfile.lock  log\tpublic\t  README.rdoc  test    vendor\r\r\n"
         | 
| 17 | 
            -
                       }
         | 
| 18 | 
            -
                cp = Hatchet::CommandParser.new(hash[:command]).parse(hash[:string])
         | 
| 19 | 
            -
                assert cp.has_valid_output?
         | 
| 20 | 
            -
                assert_equal hash[:expect], cp.to_s
         | 
| 21 | 
            -
              end
         | 
| 22 | 
            -
             | 
| 23 | 
            -
              def test_returns_result_if_no_command_in_result
         | 
| 24 | 
            -
                hash = {command: "ls",
         | 
| 25 | 
            -
                        string:  "1+1\r\r\n=> 2\r\r\n",
         | 
| 26 | 
            -
                        expect:  "1+1\r\r\n=> 2\r\r\n"
         | 
| 27 | 
            -
                       }
         | 
| 28 | 
            -
                cp = Hatchet::CommandParser.new(hash[:command]).parse(hash[:string])
         | 
| 29 | 
            -
                refute cp.has_valid_output?
         | 
| 30 | 
            -
                assert_equal hash[:expect], cp.to_s
         | 
| 31 | 
            -
              end
         | 
| 32 | 
            -
             | 
| 33 | 
            -
              def test_empty_string
         | 
| 34 | 
            -
                hash = {command: "ls",
         | 
| 35 | 
            -
                        string:  "",
         | 
| 36 | 
            -
                        expect:  ""
         | 
| 37 | 
            -
                       }
         | 
| 38 | 
            -
                cp = Hatchet::CommandParser.new(hash[:command]).parse(hash[:string])
         | 
| 39 | 
            -
                refute cp.has_valid_output?
         | 
| 40 | 
            -
                assert_equal hash[:expect], cp.to_s
         | 
| 41 | 
            -
              end
         | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
              def test_partial_command_no_result
         | 
| 45 | 
            -
                hash = {command: "1+1",
         | 
| 46 | 
            -
                        string:  "1+1\r\r\n",
         | 
| 47 | 
            -
                        expect:  "1+1\r\r\n"
         | 
| 48 | 
            -
                       }
         | 
| 49 | 
            -
                cp = Hatchet::CommandParser.new(hash[:command]).parse(hash[:string])
         | 
| 50 | 
            -
                assert_equal hash[:expect], cp.to_s
         | 
| 51 | 
            -
                refute cp.has_valid_output?
         | 
| 52 | 
            -
              end
         | 
| 53 | 
            -
            end
         | 
| 54 | 
            -
             | 
| @@ -1,20 +0,0 @@ | |
| 1 | 
            -
            require 'test_helper'
         | 
| 2 | 
            -
            require 'stringio'
         | 
| 3 | 
            -
             | 
| 4 | 
            -
            class ReplRunnerTest < Test::Unit::TestCase
         | 
| 5 | 
            -
             | 
| 6 | 
            -
              def test_returns_full_output_if_command_not_found
         | 
| 7 | 
            -
                command            = "irb"
         | 
| 8 | 
            -
                input              = StringIO.new("bar")
         | 
| 9 | 
            -
                bogus_output       = StringIO.new("foo")
         | 
| 10 | 
            -
                stream             = Hatchet::StreamExec.new(bogus_output, input, 1)
         | 
| 11 | 
            -
                repl               = Hatchet::ReplRunner.new(stream)
         | 
| 12 | 
            -
                repl.write("1+1")
         | 
| 13 | 
            -
                assert_equal bogus_output.string, repl.read("1+1")
         | 
| 14 | 
            -
             | 
| 15 | 
            -
                Hatchet::CommandParser.any_instance.expects(:parse).times(Hatchet::ReplRunner::RETRIES)
         | 
| 16 | 
            -
                Hatchet::CommandParser.any_instance.stubs(:to_s)
         | 
| 17 | 
            -
                repl.write("1+1")
         | 
| 18 | 
            -
                repl.read("1+1")
         | 
| 19 | 
            -
              end
         | 
| 20 | 
            -
            end
         | 
| @@ -1,12 +0,0 @@ | |
| 1 | 
            -
            require 'test_helper'
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            class StreamExecTest < Test::Unit::TestCase
         | 
| 4 | 
            -
              def test_local_irb_stream
         | 
| 5 | 
            -
                command            = "irb"
         | 
| 6 | 
            -
                output, input, pid = PTY.spawn(command)
         | 
| 7 | 
            -
                stream             = Hatchet::StreamExec.new(output, input, pid)
         | 
| 8 | 
            -
                stream.run("STDOUT.sync = true\n")
         | 
| 9 | 
            -
                assert_equal "1+1\r\n => 2 \r\n", stream.run("1+1\n")
         | 
| 10 | 
            -
              end
         | 
| 11 | 
            -
            end
         | 
| 12 | 
            -
             |