├── .dockerignore
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── README.md
├── cli.js
├── demo.gif
├── example_1.gif
├── example_2.gif
├── example_3.gif
├── example_4.gif
├── examples
├── basic
│ ├── autochecker.sh
│ ├── package.json
│ └── test.js
├── clojure
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── DockerTemplate
│ ├── LICENSE
│ ├── README.md
│ ├── autochecker.sh
│ ├── doc
│ │ └── intro.md
│ ├── project.clj
│ ├── src
│ │ └── autotest_example
│ │ │ └── core.clj
│ └── test
│ │ └── autotest_example
│ │ └── core_test.clj
├── dockertemplate
│ ├── DockerTemplate
│ ├── autochecker.sh
│ ├── package.json
│ └── test.js
├── golang
│ ├── DockerTemplate
│ ├── autochecker.sh
│ └── main_test.go
├── java
│ ├── Calculator.class
│ ├── Calculator.java
│ ├── CalculatorTest.class
│ ├── CalculatorTest.java
│ ├── DockerTemplate
│ └── autochecker.sh
├── php
│ ├── DockerTemplate
│ ├── autochecker.sh
│ └── test.php
├── python
│ ├── DockerTemplate
│ ├── autochecker.sh
│ └── main.py
├── readme.md
├── ruby
│ ├── .rspec
│ ├── DockerTemplate
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── autochecker.sh
│ └── spec
│ │ ├── simple_spec.rb
│ │ └── spec_helper.rb
└── run_all_examples.sh
├── index.html
├── index.js
├── package.json
└── test
└── core_test.js
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "5"
4 | sudo: required
5 | services:
6 | - docker
7 | script:
8 | - npm test
9 | - npm run prepublish
10 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### 0.9.0
2 |
3 | * Make all example tests pass instead of failing on some versions
4 | * Add bunch of more examples
5 | * Add `version` and `help` command. Usage: `autochecker version`
6 | * Start testing autochecker itself in NodeJS 6
7 | * Refactor the streams and fix the promise handling
8 |
9 | ### 0.8.0
10 |
11 | * Pull right base image, instead of always mhart/alpine-node
12 | * Use minimist for parsing arguments
13 | * Fix issue with needing to be in a Git repository for running
14 |
15 | ### 0.7.1
16 |
17 | * More renaming of verbose
18 |
19 | ### 0.7.0
20 |
21 | * Rename single_view -> verbose in the API
22 | * Enable usage of docker socket if possible, fall back to API
23 | * Move examples into examples/ folder
24 |
25 | ### 0.6.0
26 |
27 | * Possible to use non-node languages, added a ruby example project
28 |
29 | ### 0.5.2
30 |
31 | * Show errors coming from docker
32 |
33 | ### 0.5.1
34 |
35 | * Show the test statistics (# of failing/succeeded tests) after showing the errors
36 |
37 | ### 0.5.0
38 |
39 | * Show the failing versions output
40 |
41 | ### 0.4.0
42 |
43 | * New `opts` (Object with parameter) arguments for module usage, instead of 1000 arguments
44 |
45 | ### 0.3.1
46 |
47 | * CLI and library are now separated, enabling you to include autochecker directly in your project
48 | if wanted. See usage in the "Programmatic API" section above.
49 |
50 | ### 0.3.0
51 |
52 | * Add `autochecker ls` command for checking all available versions to test on
53 | * More splitting up code
54 |
55 | ### 0.2.2
56 |
57 | * Starting to split up things and start testing
58 | * Started using autochecker for checking autochecker in version 4 and 5
59 | * Limit to amount of tests running in parallel, controlled by `TEST_LIMIT` env var
60 |
61 | ### 0.2.0
62 |
63 | * Support for custom Dockerfile template (thanks to @bahmutov)
64 | * Fancier logging
65 |
66 | ### 0.1.0
67 |
68 | * Initial release
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # autochecker
2 |
3 | autochecker tests your libraries in many different versions of NodeJS, Ruby, Java and many other languages.
4 |
5 | Created to make it easier and effortless to make sure your library works in many versions of a language runtime.
6 |
7 | Works well with CI as well! ([See some example output](https://github.com/VictorBjelkholm/ruby-autochecker-example/))
8 |
9 | (Works out of the box with NodeJS projects right now, more in the future!)
10 |
11 |
12 |
13 |
14 |
15 | ## Requirements
16 |
17 | * Docker -> [install here](https://www.docker.com/products/docker-toolbox)
18 | * `package.json` `scripts.test` setup correctly (for NodeJS projects)
19 | * Two environment variables, `DOCKER_HOST` and `DOCKER_CERT_PATH` (comes by default with docker-machine)
20 |
21 | `DOCKER_HOST` should look similar to this: `tcp://192.168.99.100:2376`
22 |
23 | `DOCKER_CERT_PATH` should look similar to this: `/Users/victor/.docker/machine/machines/default`
24 |
25 | ## Installation
26 |
27 | As always, one step:
28 |
29 | * For one project > `npm install autochecker`
30 |
31 | * Globally on your computer OR to use with other languages > `npm install -g autochecker`
32 |
33 | For extra style points, make sure autochecker is run before publishing your modules:
34 |
35 | In `package.json`:
36 |
37 | ```json
38 | "scripts": {
39 | "prepublish": "autochecker 0.10 0.12 4.0 5.0"
40 | }
41 | ```
42 |
43 | ## Running NodeJS project out of the box
44 |
45 | By default, executing `autochecker` will run the tests on all available versions.
46 |
47 | You can specify which versions you want to test by adding them in the end of the command:
48 |
49 | `autochecker 0.10 0.11 4 5.10.1`
50 |
51 | Versions comes from the `mhart/alpine-node` docker image tags
52 |
53 | ## Running with other languages
54 |
55 | To see how you can run autochecker with a Ruby project + CI integration, please take a look at this repository: https://github.com/VictorBjelkholm/ruby-autochecker-example/
56 |
57 | Otherwise, there is a couple of examples of other languages in the [/examples](/examples) directory
58 |
59 | ## Setting max running tests
60 |
61 | By default, autochecker starts as many testing sessions as `os.cpu().length` would return.
62 |
63 | However, you can overwrite this by providing the TEST_LIMIT environment variable.
64 |
65 | Example: `TEST_LIMIT=10 autochecker` to run 10 test sessions at a time
66 |
67 | ## Custom Dockerfile template
68 |
69 | You can specify custom Dockerfile template if you need additional tools installed, for
70 | example if you need `git`, create a file in the project `DockerTemplate` with the following
71 |
72 | ```
73 | FROM mhart/alpine-node:$VERSION
74 | RUN mkdir -p /usr/src/app
75 | WORKDIR /usr/src/app
76 | COPY package.json .
77 | # Adding extra tools
78 | RUN apk add --update git
79 | RUN npm install
80 | COPY . .
81 | CMD npm test
82 | ```
83 |
84 | Variable `$VERSION` will be replaced by autochecker. More information about alpine images
85 | and additional tools at
86 | [docker-alpine](https://github.com/gliderlabs/docker-alpine/blob/master/docs/usage.md) and
87 | [alpine-node](https://github.com/mhart/alpine-node).
88 |
89 | Aside from adding libraries to the container, the custom template can be useful to avoid running postinstall
90 | hooks. Just use `RUN npm install --ignore-scripts` instead.
91 |
92 | ## Programmatic API
93 |
94 | You can use `autochecker` in your own projects from NodeJS directly.
95 |
96 | ```javascript
97 | var autochecker = require('autochecker')
98 | const Docker = require('dockerode')
99 | var dockerode_instance = new Docker({socketPath: '/var/run/docker.sock'});
100 | autochecker.runTestForVersion({
101 | logger: (msg) => { console.log(msg) },
102 | docker: dockerode_instance,
103 | version: '1.1.1', // version of project
104 | name: 'myproject', // name of project
105 | test_cmd: ['npm', 'test'], // command to run tests with
106 | image_name: 'app/image:commit', // What the built application image will be called
107 | path: join(__dirname, 'path_to_project'), // Path to project to build
108 | dockerfile: 'FROM nodejs:$VERSION', // Dockerfile
109 | base_image: 'base/image', // Base image, will add :$VERSION to the end
110 | verbose: false // To show full output or not
111 | })((err, results) => {
112 | console.log(results)
113 | // => {version: '1.1.1', success: true || false, output: 'output from test_cmd'}
114 | })
115 | ```
116 |
117 | See `cli.js` for usage with testing multiple versions at once.
118 |
119 | ## Changelog
120 |
121 | You can find a list of all versions and changes in the [CHANGELOG.md](CHANGELOG.md) file
122 |
123 | ## License
124 |
125 | MIT License 2016 - Victor Bjelkholm
126 |
--------------------------------------------------------------------------------
/cli.js:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 | const os = require('os')
3 | const readline = require('readline')
4 | const url = require('url')
5 | const path = require('path')
6 | const join = require('path').join
7 |
8 | // External
9 | const Docker = require('dockerode')
10 | const fs = require('fs-extra')
11 | const async = require('async')
12 | const colors = require('colors/safe')
13 | const shortid = require('shortid')
14 |
15 | // Main logic
16 | const core = require('./index')
17 |
18 | const logGreen = (msg) => console.log(colors.green(msg))
19 | const logRed = (msg) => console.log(colors.red(msg))
20 | const clearScreen = (from) => {
21 | if (from === undefined) {
22 | from = 0
23 | }
24 | readline.cursorTo(process.stdout, 0, from)
25 | readline.clearScreenDown(process.stdout)
26 | }
27 |
28 | var use_docker_socket = true
29 | const docker_host = process.env.DOCKER_HOST
30 | const docker_socket = '/var/run/docker.sock'
31 |
32 | if (!fs.existsSync(docker_socket)) {
33 | use_docker_socket = false
34 | }
35 |
36 | if (!docker_host && !use_docker_socket) {
37 | logRed('environment variable DOCKER_HOST looks empty and no socket file found at ' + docker_socket)
38 | console.log('DOCKER_HOST should be similar to this: "tcp://192.168.99.100:2376"')
39 | console.log('If you are using docker-machine, running "eval $(docker-machine env default)" should fix this')
40 | process.exit(1)
41 | }
42 |
43 | // CONFIG
44 | const getDockerTemplate = () => {
45 | const dockerTemplate = join(process.cwd(), 'DockerTemplate')
46 | if (fs.existsSync(dockerTemplate)) {
47 | return fs.readFileSync(dockerTemplate, 'utf8')
48 | }
49 | // TODO this default template, depends on which language we use
50 | const DEFAULT_DOCKER_TEMPLATE = `FROM mhart/alpine-node:$VERSION
51 | RUN mkdir -p /usr/src/app
52 | WORKDIR /usr/src/app
53 | COPY package.json /usr/src/app
54 | RUN npm install
55 | COPY . /usr/src/app
56 | CMD npm test
57 | `
58 | return DEFAULT_DOCKER_TEMPLATE
59 | }
60 | const getBaseImageFromDockerfile = (dockerfile) => {
61 | if (dockerfile.indexOf('FROM') === -1) {
62 | throw new Error('Dockerfile OR DockerTemplate does not contain FROM statement')
63 | }
64 | return dockerfile.match(/FROM\ (.*):\$VERSION/)[1]
65 | }
66 |
67 | const DOCKERFILE_TEMPLATE = getDockerTemplate()
68 |
69 | const DIRECTORY_TO_TEST = process.cwd()
70 | const TEST_COMMAND = [] // Empty, is in Dockerfile
71 |
72 | const BASE_IMAGE = getBaseImageFromDockerfile(DOCKERFILE_TEMPLATE)
73 | const PROJECT_NAME = (function getProjectName () {
74 | try {
75 | // TODO depends on language
76 | return require(DIRECTORY_TO_TEST + '/package.json').name
77 | } catch (err) {
78 | return path.basename(DIRECTORY_TO_TEST)
79 | }
80 | })()
81 |
82 | const IMAGE_NAME = `${PROJECT_NAME}_$VERSION:${shortid()}`
83 |
84 | var DOCKER_CONFIG = {}
85 | if (use_docker_socket) {
86 | DOCKER_CONFIG = {socketPath: docker_socket}
87 | } else {
88 | const protocol = 'https'
89 | const parsed_url = url.parse(process.env.DOCKER_HOST)
90 | const host = parsed_url.hostname
91 | const port = parsed_url.port
92 | const cert_path = process.env.DOCKER_CERT_PATH
93 | DOCKER_CONFIG = {
94 | protocol: protocol,
95 | host: host,
96 | port: port,
97 | ca: fs.readFileSync(join(cert_path, 'ca.pem')),
98 | cert: fs.readFileSync(join(cert_path, 'cert.pem')),
99 | key: fs.readFileSync(join(cert_path, 'key.pem'))
100 | }
101 | }
102 | // TODO implement log levels...
103 | // const LOG_LEVEL = 'default'
104 | // END CONFIG
105 |
106 | // Initialize docker
107 | const docker = new Docker(DOCKER_CONFIG)
108 |
109 | // const createErrorHandler = (version, show_output, callback) => {
110 | // return (step, err) => {
111 | // if (err) {
112 | // if (show_output) {
113 | // logRed('Something went wrong in ' + step)
114 | // } else {
115 | // logRed(version + '\t | Something went wrong in ' + step)
116 | // }
117 | // callback(err, 1)
118 | // }
119 | // }
120 | // }
121 |
122 | // TODO depends on language
123 | // From https://hub.docker.com/r/mhart/alpine-node/tags/
124 | // const possible_versions = ['0.10', '0.12', '4.0', '5.0']
125 | // TODO read this automatically from package.json.engines
126 | const default_versions_to_test = [
127 | '0.10.41',
128 | '0.10.42',
129 | '0.10.43',
130 | '0.10.44',
131 | '0.12.9',
132 | '0.12.10',
133 | '0.12.11',
134 | '0.12.12',
135 | '0.12.13',
136 | '4.2.4',
137 | '4.2.5',
138 | '4.2.6',
139 | '4.3.0',
140 | '4.3.1',
141 | '4.3.2',
142 | '4.4.0',
143 | '4.4.1',
144 | '4.4.2',
145 | '5.1.1',
146 | '5.2.0',
147 | '5.3.0',
148 | '5.4.0',
149 | '5.4.1',
150 | '5.5.0',
151 | '5.6.0',
152 | '5.7.0',
153 | '5.7.1',
154 | '5.8.0',
155 | '5.9.0',
156 | '5.9.1',
157 | '5.10.0',
158 | '5.10.1'
159 | ]
160 |
161 | const onlyFailures = (result) => {
162 | return !result.success
163 | }
164 |
165 | const testVersions = (versions) => {
166 | console.log('autochecker', 'Running tests in ' + versions.length + ' different sessions | Path: ' + DIRECTORY_TO_TEST)
167 | async.parallelLimit(versions, process.env.TEST_LIMIT || os.cpus().length, (err, results) => {
168 | if (err) {
169 | logRed('Something went wrong when running the tests...')
170 | logRed(err)
171 | console.log('Full error:')
172 | console.log(err)
173 | process.exit(23)
174 | }
175 | var any_errors = false
176 | var successes = results.filter((result) => result.success).length
177 | var failures = results.filter(onlyFailures).length
178 | console.log()
179 | if (failures > 0) {
180 | console.log(colors.red('# Failing tests'))
181 | results.filter(onlyFailures).forEach((failure) => {
182 | console.log(colors.blue('## Output from ' + failure.version))
183 | console.log(failure.output)
184 | })
185 | }
186 | console.log('== Results (Success/Fail ' + successes + '/' + failures + ') ==')
187 | results.forEach((result) => {
188 | if (result.success) {
189 | logGreen('The tests did pass on version ' + result.version)
190 | } else {
191 | // TODO Also print out the errors themselves
192 | logRed('The tests did not pass on version ' + result.version)
193 | any_errors = true
194 | }
195 | })
196 | if (any_errors) {
197 | process.exit(1)
198 | } else {
199 | process.exit(0)
200 | }
201 | })
202 | }
203 |
204 | const argv = require('minimist')(process.argv.slice(2))
205 |
206 | const cmds = {
207 | 'ls': () => {
208 | console.log('Available versions:')
209 | console.log(default_versions_to_test)
210 | return 0
211 | },
212 | 'version': () => {
213 | const current_version = require('./package.json').version
214 | console.log(current_version)
215 | return 0
216 | },
217 | 'help': () => {
218 | console.log('autochecker help')
219 | console.log()
220 | console.log('Commands: ')
221 | console.log(' autochecker ' + Object.keys(cmds).join('\n autochecker '))
222 | console.log(' autochecker 0.10 0.12')
223 | return 0
224 | }
225 | }
226 |
227 | const single_command = argv._[0]
228 |
229 | if (cmds[single_command]) {
230 | const exit_code = cmds[single_command]()
231 | process.exit(exit_code)
232 | }
233 |
234 | // Setup logging
235 | var linesToLog = {}
236 | const createLogger = (line_id, verbose) => {
237 | return (msg, color) => {
238 | if (color === undefined) {
239 | color = (str) => colors.yellow(str)
240 | }
241 | if (verbose) {
242 | console.log(color(line_id + ' - ' + msg))
243 | } else {
244 | clearScreen(1)
245 | linesToLog[line_id] = {msg, color}
246 | Object.keys(linesToLog).forEach((line, index) => {
247 | const lineToLog = linesToLog[line].msg
248 | const colorToLog = linesToLog[line].color
249 | readline.cursorTo(process.stdout, 0, index + 1)
250 | process.stdout.write(colorToLog(line + '\t- ' + lineToLog + '\n'))
251 | })
252 | }
253 | }
254 | }
255 |
256 | const runTest = (version, verbose) => {
257 | const logger = createLogger(version, verbose)
258 | return core.runTestForVersion({
259 | logger,
260 | docker,
261 | version,
262 | name: PROJECT_NAME,
263 | test_cmd: TEST_COMMAND,
264 | image_name: IMAGE_NAME,
265 | path: DIRECTORY_TO_TEST,
266 | dockerfile: DOCKERFILE_TEMPLATE,
267 | base_image: BASE_IMAGE,
268 | verbose
269 | })
270 | }
271 |
272 | const verbose = argv.verbose === true
273 |
274 | // Start testing everything
275 | // TODO extract this into cli.js and make proper
276 | if (argv._.length === 0) {
277 | clearScreen()
278 | testVersions(default_versions_to_test.map((version) => runTest(version, verbose)))
279 | } else {
280 | const to_test = argv._
281 | if (to_test.length > 1) {
282 | clearScreen()
283 | testVersions(to_test.map((version) => runTest(version, verbose)))
284 | } else {
285 | console.log(colors.green('## Running tests in version ' + colors.blue(to_test[0]) + ' only'))
286 | runTest(to_test[0], true)((err, result) => {
287 | if (err) {
288 | console.log(colors.red(err))
289 | process.exit(1)
290 | }
291 | console.log()
292 | console.log('== Results ==')
293 | if (result.success) {
294 | logGreen('The tests did pass on version ' + result.version)
295 | process.exit(0)
296 | } else {
297 | logRed('The tests did not pass on version ' + result.version)
298 | process.exit(1)
299 | }
300 | })
301 | }
302 | }
303 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victorb/autochecker/8674b6d91ce148b059ae05bcfabadbe146dc8007/demo.gif
--------------------------------------------------------------------------------
/example_1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victorb/autochecker/8674b6d91ce148b059ae05bcfabadbe146dc8007/example_1.gif
--------------------------------------------------------------------------------
/example_2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victorb/autochecker/8674b6d91ce148b059ae05bcfabadbe146dc8007/example_2.gif
--------------------------------------------------------------------------------
/example_3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victorb/autochecker/8674b6d91ce148b059ae05bcfabadbe146dc8007/example_3.gif
--------------------------------------------------------------------------------
/example_4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victorb/autochecker/8674b6d91ce148b059ae05bcfabadbe146dc8007/example_4.gif
--------------------------------------------------------------------------------
/examples/basic/autochecker.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | ../../cli.js 5.10.1 4.4.2 0.12.13 0.10.44
3 |
--------------------------------------------------------------------------------
/examples/basic/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test_project",
3 | "version": "0.0.1",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "node test.js"
8 | },
9 | "keywords": [],
10 | "author": "Victor Bjelkholm (https://www.github.com/victorbjelkholm)",
11 | "license": "MIT"
12 | }
13 |
--------------------------------------------------------------------------------
/examples/basic/test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert')
2 |
3 | assert.strictEqual(true, true)
4 |
--------------------------------------------------------------------------------
/examples/clojure/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /checkouts
4 | pom.xml
5 | pom.xml.asc
6 | *.jar
7 | *.class
8 | /.lein-*
9 | /.nrepl-port
10 | .hgignore
11 | .hg/
12 |
--------------------------------------------------------------------------------
/examples/clojure/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/).
3 |
4 | ## [Unreleased]
5 | ### Changed
6 | - Add a new arity to `make-widget-async` to provide a different widget shape.
7 |
8 | ## [0.1.1] - 2016-04-23
9 | ### Changed
10 | - Documentation on how to make the widgets.
11 |
12 | ### Removed
13 | - `make-widget-sync` - we're all async, all the time.
14 |
15 | ### Fixed
16 | - Fixed widget maker to keep working when daylight savings switches over.
17 |
18 | ## 0.1.0 - 2016-04-23
19 | ### Added
20 | - Files from the new template.
21 | - Widget maker public API - `make-widget-sync`.
22 |
23 | [Unreleased]: https://github.com/your-name/autotest_example/compare/0.1.1...HEAD
24 | [0.1.1]: https://github.com/your-name/autotest_example/compare/0.1.0...0.1.1
25 |
--------------------------------------------------------------------------------
/examples/clojure/DockerTemplate:
--------------------------------------------------------------------------------
1 | FROM clojure:$VERSION
2 | COPY . /usr/src/app
3 | WORKDIR /usr/src/app
4 | CMD ["lein", "test"]
5 |
--------------------------------------------------------------------------------
/examples/clojure/LICENSE:
--------------------------------------------------------------------------------
1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
4 |
5 | 1. DEFINITIONS
6 |
7 | "Contribution" means:
8 |
9 | a) in the case of the initial Contributor, the initial code and
10 | documentation distributed under this Agreement, and
11 |
12 | b) in the case of each subsequent Contributor:
13 |
14 | i) changes to the Program, and
15 |
16 | ii) additions to the Program;
17 |
18 | where such changes and/or additions to the Program originate from and are
19 | distributed by that particular Contributor. A Contribution 'originates' from
20 | a Contributor if it was added to the Program by such Contributor itself or
21 | anyone acting on such Contributor's behalf. Contributions do not include
22 | additions to the Program which: (i) are separate modules of software
23 | distributed in conjunction with the Program under their own license
24 | agreement, and (ii) are not derivative works of the Program.
25 |
26 | "Contributor" means any person or entity that distributes the Program.
27 |
28 | "Licensed Patents" mean patent claims licensable by a Contributor which are
29 | necessarily infringed by the use or sale of its Contribution alone or when
30 | combined with the Program.
31 |
32 | "Program" means the Contributions distributed in accordance with this
33 | Agreement.
34 |
35 | "Recipient" means anyone who receives the Program under this Agreement,
36 | including all Contributors.
37 |
38 | 2. GRANT OF RIGHTS
39 |
40 | a) Subject to the terms of this Agreement, each Contributor hereby grants
41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to
42 | reproduce, prepare derivative works of, publicly display, publicly perform,
43 | distribute and sublicense the Contribution of such Contributor, if any, and
44 | such derivative works, in source code and object code form.
45 |
46 | b) Subject to the terms of this Agreement, each Contributor hereby grants
47 | Recipient a non-exclusive, worldwide, royalty-free patent license under
48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise
49 | transfer the Contribution of such Contributor, if any, in source code and
50 | object code form. This patent license shall apply to the combination of the
51 | Contribution and the Program if, at the time the Contribution is added by the
52 | Contributor, such addition of the Contribution causes such combination to be
53 | covered by the Licensed Patents. The patent license shall not apply to any
54 | other combinations which include the Contribution. No hardware per se is
55 | licensed hereunder.
56 |
57 | c) Recipient understands that although each Contributor grants the licenses
58 | to its Contributions set forth herein, no assurances are provided by any
59 | Contributor that the Program does not infringe the patent or other
60 | intellectual property rights of any other entity. Each Contributor disclaims
61 | any liability to Recipient for claims brought by any other entity based on
62 | infringement of intellectual property rights or otherwise. As a condition to
63 | exercising the rights and licenses granted hereunder, each Recipient hereby
64 | assumes sole responsibility to secure any other intellectual property rights
65 | needed, if any. For example, if a third party patent license is required to
66 | allow Recipient to distribute the Program, it is Recipient's responsibility
67 | to acquire that license before distributing the Program.
68 |
69 | d) Each Contributor represents that to its knowledge it has sufficient
70 | copyright rights in its Contribution, if any, to grant the copyright license
71 | set forth in this Agreement.
72 |
73 | 3. REQUIREMENTS
74 |
75 | A Contributor may choose to distribute the Program in object code form under
76 | its own license agreement, provided that:
77 |
78 | a) it complies with the terms and conditions of this Agreement; and
79 |
80 | b) its license agreement:
81 |
82 | i) effectively disclaims on behalf of all Contributors all warranties and
83 | conditions, express and implied, including warranties or conditions of title
84 | and non-infringement, and implied warranties or conditions of merchantability
85 | and fitness for a particular purpose;
86 |
87 | ii) effectively excludes on behalf of all Contributors all liability for
88 | damages, including direct, indirect, special, incidental and consequential
89 | damages, such as lost profits;
90 |
91 | iii) states that any provisions which differ from this Agreement are offered
92 | by that Contributor alone and not by any other party; and
93 |
94 | iv) states that source code for the Program is available from such
95 | Contributor, and informs licensees how to obtain it in a reasonable manner on
96 | or through a medium customarily used for software exchange.
97 |
98 | When the Program is made available in source code form:
99 |
100 | a) it must be made available under this Agreement; and
101 |
102 | b) a copy of this Agreement must be included with each copy of the Program.
103 |
104 | Contributors may not remove or alter any copyright notices contained within
105 | the Program.
106 |
107 | Each Contributor must identify itself as the originator of its Contribution,
108 | if any, in a manner that reasonably allows subsequent Recipients to identify
109 | the originator of the Contribution.
110 |
111 | 4. COMMERCIAL DISTRIBUTION
112 |
113 | Commercial distributors of software may accept certain responsibilities with
114 | respect to end users, business partners and the like. While this license is
115 | intended to facilitate the commercial use of the Program, the Contributor who
116 | includes the Program in a commercial product offering should do so in a
117 | manner which does not create potential liability for other Contributors.
118 | Therefore, if a Contributor includes the Program in a commercial product
119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend
120 | and indemnify every other Contributor ("Indemnified Contributor") against any
121 | losses, damages and costs (collectively "Losses") arising from claims,
122 | lawsuits and other legal actions brought by a third party against the
123 | Indemnified Contributor to the extent caused by the acts or omissions of such
124 | Commercial Contributor in connection with its distribution of the Program in
125 | a commercial product offering. The obligations in this section do not apply
126 | to any claims or Losses relating to any actual or alleged intellectual
127 | property infringement. In order to qualify, an Indemnified Contributor must:
128 | a) promptly notify the Commercial Contributor in writing of such claim, and
129 | b) allow the Commercial Contributor tocontrol, and cooperate with the
130 | Commercial Contributor in, the defense and any related settlement
131 | negotiations. The Indemnified Contributor may participate in any such claim
132 | at its own expense.
133 |
134 | For example, a Contributor might include the Program in a commercial product
135 | offering, Product X. That Contributor is then a Commercial Contributor. If
136 | that Commercial Contributor then makes performance claims, or offers
137 | warranties related to Product X, those performance claims and warranties are
138 | such Commercial Contributor's responsibility alone. Under this section, the
139 | Commercial Contributor would have to defend claims against the other
140 | Contributors related to those performance claims and warranties, and if a
141 | court requires any other Contributor to pay any damages as a result, the
142 | Commercial Contributor must pay those damages.
143 |
144 | 5. NO WARRANTY
145 |
146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON
147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR
149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A
150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the
151 | appropriateness of using and distributing the Program and assumes all risks
152 | associated with its exercise of rights under this Agreement , including but
153 | not limited to the risks and costs of program errors, compliance with
154 | applicable laws, damage to or loss of data, programs or equipment, and
155 | unavailability or interruption of operations.
156 |
157 | 6. DISCLAIMER OF LIABILITY
158 |
159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
166 | OF SUCH DAMAGES.
167 |
168 | 7. GENERAL
169 |
170 | If any provision of this Agreement is invalid or unenforceable under
171 | applicable law, it shall not affect the validity or enforceability of the
172 | remainder of the terms of this Agreement, and without further action by the
173 | parties hereto, such provision shall be reformed to the minimum extent
174 | necessary to make such provision valid and enforceable.
175 |
176 | If Recipient institutes patent litigation against any entity (including a
177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself
178 | (excluding combinations of the Program with other software or hardware)
179 | infringes such Recipient's patent(s), then such Recipient's rights granted
180 | under Section 2(b) shall terminate as of the date such litigation is filed.
181 |
182 | All Recipient's rights under this Agreement shall terminate if it fails to
183 | comply with any of the material terms or conditions of this Agreement and
184 | does not cure such failure in a reasonable period of time after becoming
185 | aware of such noncompliance. If all Recipient's rights under this Agreement
186 | terminate, Recipient agrees to cease use and distribution of the Program as
187 | soon as reasonably practicable. However, Recipient's obligations under this
188 | Agreement and any licenses granted by Recipient relating to the Program shall
189 | continue and survive.
190 |
191 | Everyone is permitted to copy and distribute copies of this Agreement, but in
192 | order to avoid inconsistency the Agreement is copyrighted and may only be
193 | modified in the following manner. The Agreement Steward reserves the right to
194 | publish new versions (including revisions) of this Agreement from time to
195 | time. No one other than the Agreement Steward has the right to modify this
196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The
197 | Eclipse Foundation may assign the responsibility to serve as the Agreement
198 | Steward to a suitable separate entity. Each new version of the Agreement will
199 | be given a distinguishing version number. The Program (including
200 | Contributions) may always be distributed subject to the version of the
201 | Agreement under which it was received. In addition, after a new version of
202 | the Agreement is published, Contributor may elect to distribute the Program
203 | (including its Contributions) under the new version. Except as expressly
204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
205 | licenses to the intellectual property of any Contributor under this
206 | Agreement, whether expressly, by implication, estoppel or otherwise. All
207 | rights in the Program not expressly granted under this Agreement are
208 | reserved.
209 |
210 | This Agreement is governed by the laws of the State of New York and the
211 | intellectual property laws of the United States of America. No party to this
212 | Agreement will bring a legal action under this Agreement more than one year
213 | after the cause of action arose. Each party waives its rights to a jury trial
214 | in any resulting litigation.
215 |
--------------------------------------------------------------------------------
/examples/clojure/README.md:
--------------------------------------------------------------------------------
1 | # autotest_example
2 |
3 | A Clojure library designed to ... well, that part is up to you.
4 |
5 | ## Usage
6 |
7 | FIXME
8 |
9 | ## License
10 |
11 | Copyright © 2016 FIXME
12 |
13 | Distributed under the Eclipse Public License either version 1.0 or (at
14 | your option) any later version.
15 |
--------------------------------------------------------------------------------
/examples/clojure/autochecker.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | ../../cli.js lein-2.6.1 lein-2.4.3
3 |
--------------------------------------------------------------------------------
/examples/clojure/doc/intro.md:
--------------------------------------------------------------------------------
1 | # Introduction to autotest_example
2 |
3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/)
4 |
--------------------------------------------------------------------------------
/examples/clojure/project.clj:
--------------------------------------------------------------------------------
1 | (defproject autotest_example "0.1.0-SNAPSHOT"
2 | :description "FIXME: write description"
3 | :url "http://example.com/FIXME"
4 | :license {:name "Eclipse Public License"
5 | :url "http://www.eclipse.org/legal/epl-v10.html"}
6 | :dependencies [[org.clojure/clojure "1.8.0"]])
7 |
--------------------------------------------------------------------------------
/examples/clojure/src/autotest_example/core.clj:
--------------------------------------------------------------------------------
1 | (ns autotest-example.core)
2 |
3 | (defn foo
4 | "I don't do a whole lot."
5 | [x]
6 | (println x "Hello, World!"))
7 |
--------------------------------------------------------------------------------
/examples/clojure/test/autotest_example/core_test.clj:
--------------------------------------------------------------------------------
1 | (ns autotest-example.core-test
2 | (:require [clojure.test :refer :all]
3 | [autotest-example.core :refer :all]))
4 |
5 | (deftest a-test
6 | (testing "FIXME, I fail."
7 | (is (= 1 1))))
8 |
--------------------------------------------------------------------------------
/examples/dockertemplate/DockerTemplate:
--------------------------------------------------------------------------------
1 | FROM mhart/alpine-node:$VERSION
2 | RUN mkdir -p /usr/src/app
3 | WORKDIR /usr/src/app
4 | COPY package.json .
5 | # Adding extra tools
6 | RUN apk add --update git
7 | RUN npm install
8 | COPY . .
9 | CMD npm test
10 |
--------------------------------------------------------------------------------
/examples/dockertemplate/autochecker.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | ../../cli.js 5.10.1 4.4.2 0.12.13 0.10.44
3 |
--------------------------------------------------------------------------------
/examples/dockertemplate/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test_project",
3 | "version": "0.0.1",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "node test.js"
8 | },
9 | "keywords": [],
10 | "author": "Victor Bjelkholm (https://www.github.com/victorbjelkholm)",
11 | "license": "MIT"
12 | }
13 |
--------------------------------------------------------------------------------
/examples/dockertemplate/test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert')
2 |
3 | assert.strictEqual(true, true)
4 |
--------------------------------------------------------------------------------
/examples/golang/DockerTemplate:
--------------------------------------------------------------------------------
1 | FROM golang:$VERSION
2 |
3 | RUN mkdir -p /usr/src/app
4 |
5 | WORKDIR /usr/src/app
6 |
7 | COPY . .
8 |
9 | CMD go test
10 |
--------------------------------------------------------------------------------
/examples/golang/autochecker.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | ../../cli.js 1.5 1.6 1.6.1 1.6.2
3 |
--------------------------------------------------------------------------------
/examples/golang/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func sum(a, b int) int {
8 | return a + b
9 | }
10 |
11 | func TestSum(t *testing.T) {
12 | if want, got := 5, sum(2, 3); got != want {
13 | t.Fatalf("Expecting %d, but got %d\n", want, got)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/java/Calculator.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victorb/autochecker/8674b6d91ce148b059ae05bcfabadbe146dc8007/examples/java/Calculator.class
--------------------------------------------------------------------------------
/examples/java/Calculator.java:
--------------------------------------------------------------------------------
1 | public class Calculator {
2 | public int evaluate(String expression) {
3 | int sum = 0;
4 | for (String summand: expression.split("\\+"))
5 | sum += Integer.valueOf(summand);
6 | return sum;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/java/CalculatorTest.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victorb/autochecker/8674b6d91ce148b059ae05bcfabadbe146dc8007/examples/java/CalculatorTest.class
--------------------------------------------------------------------------------
/examples/java/CalculatorTest.java:
--------------------------------------------------------------------------------
1 | import static org.junit.Assert.assertEquals;
2 | import org.junit.Test;
3 | // TODO Make to fail on different version
4 |
5 | public class CalculatorTest {
6 | @Test
7 | public void evaluatesExpression() {
8 | Calculator calculator = new Calculator();
9 | int sum = calculator.evaluate("1+2+3");
10 | assertEquals(6, sum);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/java/DockerTemplate:
--------------------------------------------------------------------------------
1 | FROM java:$VERSION
2 |
3 | WORKDIR /usr/src/app
4 |
5 | RUN apk --update add wget ca-certificates
6 |
7 | RUN wget https://github.com/junit-team/junit4/releases/download/r4.12/junit-4.12.jar -O junit-4.12.jar
8 | RUN wget search.maven.org/remotecontent?filepath=org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar -O hamcrest-core-1.3.jar
9 |
10 | COPY Calculator.java .
11 | COPY CalculatorTest.java .
12 |
13 | RUN javac Calculator.java
14 | RUN javac -cp .:junit-4.12.jar CalculatorTest.java
15 |
16 | CMD java -cp .:junit-4.12.jar:hamcrest-core-1.3.jar org.junit.runner.JUnitCore CalculatorTest
17 |
--------------------------------------------------------------------------------
/examples/java/autochecker.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | ../../cli.js openjdk-8-jdk-alpine openjdk-7-jdk-alpine
3 |
--------------------------------------------------------------------------------
/examples/php/DockerTemplate:
--------------------------------------------------------------------------------
1 | FROM php:$VERSION
2 |
3 | WORKDIR /usr/src/app
4 |
5 | RUN wget https://phar.phpunit.de/phpunit.phar
6 |
7 | COPY . /usr/src/app
8 |
9 | CMD php phpunit.phar test.php
10 |
--------------------------------------------------------------------------------
/examples/php/autochecker.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | # Bu, only one alpine version available :/
3 | ../../cli.js 7.0-alpine 5.6.20-alpine
4 |
--------------------------------------------------------------------------------
/examples/php/test.php:
--------------------------------------------------------------------------------
1 | amount = $amount;
9 | }
10 |
11 | public function getAmount()
12 | {
13 | return $this->amount;
14 | }
15 |
16 | public function negate()
17 | {
18 | return new Money(-1 * $this->amount);
19 | }
20 | }
21 |
22 | class MoneyTest extends PHPUnit_Framework_TestCase
23 | {
24 | public function testCanBeNegated()
25 | {
26 | // Arrange
27 | $a = new Money(1);
28 |
29 | // Act
30 | $b = $a->negate();
31 |
32 | // Assert
33 | $this->assertEquals(-1, $b->getAmount());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/examples/python/DockerTemplate:
--------------------------------------------------------------------------------
1 | FROM python:$VERSION
2 |
3 | RUN mkdir -p /usr/src/app
4 |
5 | WORKDIR /usr/src/app
6 |
7 | COPY . .
8 |
9 | CMD python main.py
10 |
--------------------------------------------------------------------------------
/examples/python/autochecker.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | ../../cli.js 2.7.11-alpine 3.5.1-alpine
3 |
--------------------------------------------------------------------------------
/examples/python/main.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | def fun(x):
4 | print("Called fun")
5 | return x + 1
6 |
7 | class MyTest(unittest.TestCase):
8 | def test(self):
9 | self.assertEqual(fun(3), 4)
10 |
11 | unittest.main()
12 |
--------------------------------------------------------------------------------
/examples/readme.md:
--------------------------------------------------------------------------------
1 | ## autochecker examples
2 |
3 | This is a collection of examples on how to run autochecker with different languages and configurations
4 |
5 | Typically, a example looks like this:
6 |
7 | ```
8 | directory >
9 | | autochecker.sh # for running autochecker
10 | | DockerTemplate # for telling autochecker how to build
11 | \ source_code # the rest of the files for language/configuration that is needed
12 | ```
13 |
14 | ## To run all of them:
15 |
16 | `./run_all_examples.sh`
17 |
18 | ## Languages
19 |
20 | * [x] JavaScript
21 | * [x] Ruby
22 | * [x] Java
23 | * [ ] Swift
24 | * [x] Golang
25 | * [ ] C#
26 | * [x] Python
27 | * [ ] Objective-C
28 | * [ ] C++
29 | * [ ] Perl
30 | * [ ] Scala
31 | * [x] Clojure
32 | * [x] PHP
33 | * [ ] Lua
34 | * [ ] Haskell
35 | * [ ] Rust
36 |
37 | If you know any language here that haven't been checked, please help others out and try create a example for it!
38 |
--------------------------------------------------------------------------------
/examples/ruby/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --require spec_helper
3 |
--------------------------------------------------------------------------------
/examples/ruby/DockerTemplate:
--------------------------------------------------------------------------------
1 | FROM ruby:$VERSION
2 |
3 | RUN mkdir -p /usr/src/app
4 |
5 | WORKDIR /usr/src/app
6 |
7 | COPY Gemfile .
8 | COPY Gemfile.lock .
9 |
10 | RUN bundle
11 |
12 | COPY . .
13 |
14 | CMD bundle exec rspec
15 |
--------------------------------------------------------------------------------
/examples/ruby/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "rspec"
4 |
--------------------------------------------------------------------------------
/examples/ruby/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | diff-lcs (1.2.5)
5 | rspec (3.4.0)
6 | rspec-core (~> 3.4.0)
7 | rspec-expectations (~> 3.4.0)
8 | rspec-mocks (~> 3.4.0)
9 | rspec-core (3.4.4)
10 | rspec-support (~> 3.4.0)
11 | rspec-expectations (3.4.0)
12 | diff-lcs (>= 1.2.0, < 2.0)
13 | rspec-support (~> 3.4.0)
14 | rspec-mocks (3.4.1)
15 | diff-lcs (>= 1.2.0, < 2.0)
16 | rspec-support (~> 3.4.0)
17 | rspec-support (3.4.1)
18 |
19 | PLATFORMS
20 | ruby
21 |
22 | DEPENDENCIES
23 | rspec
24 |
25 | BUNDLED WITH
26 | 1.11.2
27 |
--------------------------------------------------------------------------------
/examples/ruby/autochecker.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | ../../cli.js 2.1.8-alpine 2.3.0-alpine 2.1-alpine 2.1.9-alpine 2.2.4-alpine
3 |
--------------------------------------------------------------------------------
/examples/ruby/spec/simple_spec.rb:
--------------------------------------------------------------------------------
1 | describe Integer do
2 | it "tests something basic" do
3 | expect(true).to eql(true)
4 | expect(false).to eql(false)
5 | expect(nil).to eql(nil)
6 | end
7 | end
8 |
9 |
--------------------------------------------------------------------------------
/examples/ruby/spec/spec_helper.rb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victorb/autochecker/8674b6d91ce148b059ae05bcfabadbe146dc8007/examples/ruby/spec/spec_helper.rb
--------------------------------------------------------------------------------
/examples/run_all_examples.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 |
3 | set -e
4 |
5 | for d in $(ls)
6 | do
7 | test -d "$d" || continue
8 | echo "Running tests for $d"
9 | ( cd $d && time sh autochecker.sh )
10 | done
11 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | autochecker - test your libraries in many different versions of NodeJS, Ruby, Java and many other languages.
8 |
9 |
10 |
63 |
64 |
65 |
66 |
67 |
autochecker
68 |
autochecker tests your libraries in many different versions of NodeJS, Ruby, Java and many other languages.
69 |
70 |
71 |
72 | Imagine you have either a library or an application that you need to maintain to work on many
73 | different versions of NodeJS, or want to make sure it works in the latest versions.
74 |
75 |
76 | Then autochecker will help you with exactly that!
77 |
78 |
79 |
80 |
Requirements
81 |
82 | NodeJS version 4.2.4 or higher
83 | Docker
84 | DOCKER_HOST and DOCKER_CERT_PATH environment variables (that comes with docker-machine by default)
85 |
86 |
87 |
88 |
Installation
89 |
Globally (or usage in other languages than NodeJS):
90 |
npm install -g autochecker
91 |
Or just in your project:
92 |
npm install --save-dev autochecker
93 |
94 |
95 |
Examples
96 |
97 |
autochecker
98 |
All available versions tested
99 |
100 |
101 |
102 |
autochecker 0.10 5.8
103 |
Only specified versions tested
104 |
105 |
106 |
107 |
autochecker 0.10
108 |
Only one specified version tested with verbose output
109 |
110 |
111 |
112 |
TEST_LIMIT=10 autochecker
113 |
Set the limit of number of parallel tests
114 |
115 |
116 |
123 |
124 |
125 |
You like autochecker?
126 |
Want to tell people? Tweet about it!
127 |
128 |
129 |
You want to help out? Visit the Github repository!
130 |
131 |
132 |
133 |
134 |
141 |
142 |
143 |
144 |
145 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This is the file for the main logic of the application.
3 | * Everything basically is here, everything else is just gluing this together
4 | *
5 | */
6 | const fs = require('fs-extra')
7 | const stream = require('stream')
8 | const colors = require('colors')
9 | const tar = require('tar-fs')
10 |
11 | const returnOrThrow = (attribute, name) => {
12 | if (attribute === undefined) {
13 | throw new Error('Option ' + name + ' was undefined but it needs to be defined')
14 | }
15 | return attribute
16 | }
17 |
18 | const assertObjectContainKeys = (obj, keys) => {
19 | if (!obj) {
20 | throw new Error('Object was null/undefined/false')
21 | }
22 | const obj_keys = Object.keys(obj)
23 | keys.forEach((cur_key) => {
24 | if (obj_keys.indexOf(cur_key) === -1) {
25 | throw new Error('Object did not contain key "' + cur_key + '"')
26 | }
27 | if (obj[cur_key] === undefined) {
28 | throw new Error('Object had key "' + cur_key + '" but it was undefined')
29 | }
30 | })
31 | }
32 |
33 | const setVersionInDockerfile = (version, dockerfile) => {
34 | if (dockerfile.indexOf('$VERSION') === -1) {
35 | throw new Error('Dockerfile did not contain $VERSION')
36 | }
37 | return dockerfile.replace('$VERSION', version)
38 | }
39 | const parseStreamChunk = (chunk) => {
40 | const chunkStr = chunk.toString()
41 | const splitted = chunkStr.split('\r\n')
42 | const parsedLines = splitted.map((str) => {
43 | try {
44 | return JSON.parse(str)
45 | } catch (_) {
46 | return null
47 | }
48 | })
49 | return parsedLines.filter((l) => l !== null)
50 | }
51 | const pullImage = (opts) => {
52 | assertObjectContainKeys(opts, [
53 | 'docker',
54 | 'base_image',
55 | 'version',
56 | 'verbose'
57 | ])
58 | return new Promise((resolve, reject) => {
59 | // TODO in the future, check if image already exists, and skip pulling
60 | const should_pull = true
61 | if (should_pull) {
62 | opts.docker.pull(`${opts.base_image}:${opts.version}`, (err, stream) => {
63 | if (err) {
64 | return reject(err)
65 | }
66 | stream.on('data', (chunk) => {
67 | // TODO when pulling, chunk also have progress property we should print
68 | // {"status":"Extracting","progressDetail":{"current":10310894,"total":10310894},
69 | // "progress":"[==================================================\u003e] 10.31 MB/10.31 MB",
70 | // "id":"c9590ff90c14"}
71 | if (opts.verbose) {
72 | const chunks = parseStreamChunk(chunk)
73 | chunks.forEach((mini_chunk) => {
74 | const status = mini_chunk.status
75 | if (status !== 'Downloading' && status !== 'Extracting') {
76 | console.log(mini_chunk.status)
77 | }
78 | })
79 | }
80 | })
81 | stream.on('end', () => {
82 | return resolve(err)
83 | })
84 | })
85 | } else {
86 | return resolve(null)
87 | }
88 | })
89 | }
90 | const buildImage = (opts) => {
91 | assertObjectContainKeys(opts, [
92 | 'docker',
93 | 'path',
94 | 'image_name',
95 | 'dockerfile',
96 | 'verbose'
97 | ])
98 | return new Promise((resolve, reject) => {
99 | if (!fs.existsSync(opts.path)) {
100 | return reject('Path "' + opts.path + '" does not exist')
101 | }
102 | const tarStream = tar.pack(opts.path)
103 | tarStream.entry({ name: 'Dockerfile' }, opts.dockerfile)
104 | opts.docker.buildImage(tarStream, {t: opts.image_name}, (err, stream) => {
105 | if (err) {
106 | return reject(err)
107 | }
108 | if (!stream) {
109 | return reject()
110 | }
111 | stream.on('data', (chunk) => {
112 | const chunks = parseStreamChunk(chunk)
113 | if (chunks.length && chunks[chunks.length - 1].error) {
114 | return reject(chunks[chunks.length - 1].error)
115 | }
116 | if (opts.verbose) {
117 | chunks.forEach((mini_chunk) => process.stdout.write(mini_chunk.stream))
118 | }
119 | })
120 | stream.on('end', () => {
121 | return resolve(err)
122 | })
123 | })
124 | })
125 | }
126 | const filterOutputStream = (chunk) => {
127 | return chunk.toString().split('\r\n').filter((line) => {
128 | return line !== null && line !== undefined && line.trim() !== ''
129 | })
130 | }
131 | const runContainer = (opts) => {
132 | assertObjectContainKeys(opts, [
133 | 'docker',
134 | 'image_name',
135 | 'test_cmd',
136 | 'verbose'
137 | ])
138 | return new Promise((resolve, reject) => {
139 | var output = []
140 | const collect_output_stream = new stream.Writable({
141 | write: (chunk, encoding, next) => {
142 | if (opts.verbose) {
143 | filterOutputStream(chunk).forEach((line) => {
144 | console.log(line)
145 | })
146 | }
147 | output.push(chunk.toString())
148 | next()
149 | }
150 | })
151 | // const outputter = verbose ? process.stdout : collect_output_stream
152 | opts.docker.run(opts.image_name, opts.test_cmd, collect_output_stream, (err, data) => {
153 | if (err) {
154 | return reject(err)
155 | }
156 | if (!data) {
157 | return reject()
158 | }
159 | var to_resolve = {success: data.StatusCode === 0}
160 | to_resolve.output = output.join('')
161 | return resolve(to_resolve)
162 | })
163 | })
164 | }
165 | const runTestForVersion = (opts) => {
166 | const logger = returnOrThrow(opts.logger, 'logger')
167 | const docker = returnOrThrow(opts.docker, 'docker')
168 | const version = returnOrThrow(opts.version, 'version')
169 | const test_cmd = returnOrThrow(opts.test_cmd, 'test_cmd')
170 | const image_name = returnOrThrow(opts.image_name, 'image_name')
171 | const path = returnOrThrow(opts.path, 'path')
172 | const dockerfile_raw = returnOrThrow(opts.dockerfile, 'dockerfile')
173 | const base_image = returnOrThrow(opts.base_image, 'base_image')
174 | const verbose = returnOrThrow(opts.verbose, 'verbose')
175 |
176 | const versioned_image_name = image_name.replace('$VERSION', version)
177 | const dockerfile = setVersionInDockerfile(version, dockerfile_raw)
178 |
179 | const cmd_opts = {
180 | docker,
181 | base_image,
182 | image_name: versioned_image_name,
183 | version,
184 | verbose,
185 | logger,
186 | path,
187 | test_cmd,
188 | dockerfile
189 | }
190 |
191 | return (callback) => {
192 | logger(`pulling image ${base_image}:${version}`)
193 | pullImage(cmd_opts).then(() => {
194 | logger('building image')
195 | return buildImage(cmd_opts)
196 | }).then(() => {
197 | logger('running container')
198 | return runContainer(cmd_opts)
199 | }).then((res) => {
200 | const success = res.success
201 | const output = res.output
202 | logger(success ? colors.green('Test results: ✅') : colors.red('Test results: ❌'))
203 | callback(null, {success, version, output})
204 | }).catch((err) => {
205 | callback(err)
206 | })
207 | }
208 | }
209 | module.exports = {
210 | runTestForVersion,
211 | pullImage,
212 | buildImage,
213 | runContainer
214 | }
215 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "autochecker",
3 | "version": "0.9.2",
4 | "description": "Test your libraries in many different versions of NodeJS, Ruby, Java and many other languages",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha",
8 | "test:watch": "npm test -- --watch --growl",
9 | "lint": "standard",
10 | "prepublish": "./cli.js 4.4.2 5.10.1 6.0.0"
11 | },
12 | "bin": {
13 | "autochecker": "./cli.js"
14 | },
15 | "keywords": [
16 | "testing",
17 | "versions",
18 | "nodejs"
19 | ],
20 | "homepage": "http://victorbjelkholm.github.io/autochecker/",
21 | "bugs": {
22 | "url": "https://github.com/victorbjelkholm/autochecker/issues",
23 | "email": "victorbjelkholm@gmail.com"
24 | },
25 | "repository": {
26 | "type": "git",
27 | "url": "https://github.com/VictorBjelkholm/autochecker.git"
28 | },
29 | "engines": {
30 | "node": ">=4.2.4"
31 | },
32 | "preferGlobal": true,
33 | "author": "Victor Bjelkholm (https://www.github.com/victorbjelkholm)",
34 | "license": "MIT",
35 | "dependencies": {
36 | "async": "2.0.0-rc.3",
37 | "colors": "1.1.2",
38 | "dockerode": "2.2.10",
39 | "fs-extra": "0.26.7",
40 | "minimist": "1.2.0",
41 | "shortid": "2.2.6",
42 | "tar-fs": "1.12.0"
43 | },
44 | "devDependencies": {
45 | "chai": "3.5.0",
46 | "chai-as-promised": "5.3.0",
47 | "mocha": "2.4.5",
48 | "standard": "6.0.8"
49 | },
50 | "files": [
51 | ".dockerignore",
52 | "index.js",
53 | "cli.js",
54 | "demo.gif"
55 | ]
56 | }
57 |
--------------------------------------------------------------------------------
/test/core_test.js:
--------------------------------------------------------------------------------
1 | /* global describe, it, after, beforeEach, xit */
2 | const chai = require('chai')
3 | const chaiAsPromised = require('chai-as-promised')
4 | chai.use(chaiAsPromised)
5 |
6 | const assert = chai.assert
7 | const fs = require('fs-extra')
8 | const join = require('path').join
9 | const stream = require('stream')
10 | const core = require('../index')
11 |
12 | const createMockStreamFunc = () => {
13 | return (image, options, callback) => {
14 | if (callback === undefined) {
15 | callback = options
16 | }
17 | const fake_stream = new stream.Readable()
18 | callback(docker_mock_error_value, fake_stream)
19 | fake_stream._read = () => {
20 | }
21 | fake_stream.emit('end')
22 | }
23 | }
24 |
25 | const PACKAGE_FILE = `{
26 | "name": "test_project",
27 | "version": "0.0.1",
28 | "description": "",
29 | "main": "index.js",
30 | "scripts": {
31 | "test": "echo "I'm gonna fail..."; exit 1"
32 | },
33 | "keywords": [],
34 | "license": "MIT"
35 | }`
36 |
37 | const writeTestProject = () => {
38 | fs.mkdirsSync(join(__dirname, 'test_project'))
39 | fs.mkdirsSync(join(__dirname, 'test_project', '.git'))
40 | fs.mkdirsSync(join(__dirname, 'test_project', 'node_modules'))
41 | fs.writeFileSync(join(__dirname, 'test_project', 'package.json'), JSON.stringify(PACKAGE_FILE, null, 2))
42 | }
43 |
44 | var docker_mock_error_value = null
45 | var docker_mock_run_statuscode = 0
46 | const createDockerMock = () => {
47 | return {
48 | pull: createMockStreamFunc(),
49 | buildImage: createMockStreamFunc(),
50 | run: (image_name, cmd, output, callback) => {
51 | callback(docker_mock_error_value, {
52 | StatusCode: docker_mock_run_statuscode
53 | })
54 | }
55 | }
56 | }
57 | beforeEach(() => {
58 | docker_mock_error_value = null
59 | })
60 | after((done) => {
61 | fs.remove(join(__dirname, 'tmp_test_project'), () => {
62 | fs.remove(join(__dirname, 'test_project'), () => {
63 | done()
64 | })
65 | })
66 | })
67 | describe('Application Core Logic', () => {
68 | describe('Pulling a image', () => {
69 | const pull_opts = {
70 | docker: createDockerMock(),
71 | base_image: 'small/image',
72 | version: 'myversion',
73 | verbose: false
74 | }
75 | it('Pull an image with docker', () => {
76 | return assert.isFulfilled(core.pullImage(pull_opts))
77 | })
78 | it('Can fail while pulling image', () => {
79 | docker_mock_error_value = new Error('Something went wrong')
80 | return assert.isRejected(core.pullImage(pull_opts), /Something went wrong/)
81 | })
82 | xit('Logs data if wanted')
83 | })
84 | describe('Building a image', () => {
85 | const build_opts = {
86 | docker: createDockerMock(),
87 | path: join(__dirname, 'test_project'),
88 | image_name: 'my/image',
89 | dockerfile: 'dockerfile',
90 | verbose: false
91 | }
92 | beforeEach(writeTestProject)
93 | it('Builds a image from path', () => {
94 | return assert.isFulfilled(core.buildImage(build_opts))
95 | })
96 | it('Build fails if directory does not exists', () => {
97 | build_opts.path = '/somemadeuppath/'
98 | const promise = assert.isRejected(core.buildImage(build_opts))
99 | build_opts.path = join(__dirname, 'test_project')
100 | return promise
101 | })
102 | it('Can fail while building image', () => {
103 | docker_mock_error_value = new Error('Something went wrong')
104 | return assert.isRejected(
105 | core.buildImage(build_opts),
106 | /Something went wrong/
107 | )
108 | })
109 | })
110 | describe('Run a container', () => {
111 | const run_opts = {
112 | docker: createDockerMock(),
113 | image_name: 'my/image',
114 | test_cmd: ['whoami'],
115 | verbose: false
116 | }
117 | it('Runs a container', () => {
118 | return assert.isFulfilled(core.runContainer(run_opts))
119 | })
120 | it('Tells when the command succeeded', (done) => {
121 | docker_mock_run_statuscode = 0
122 | core.runContainer(run_opts).then((res) => {
123 | assert.strictEqual(res.success, true)
124 | done()
125 | }).catch(done)
126 | })
127 | it('Tells when the command fails', (done) => {
128 | docker_mock_run_statuscode = 1
129 | core.runContainer(run_opts).then((res) => {
130 | assert.strictEqual(res.success, false)
131 | done()
132 | }).catch(done)
133 | })
134 | })
135 | describe('Can run all commands with runTestsForVersion', () => {
136 | const testRun = () => {
137 | return core.runTestForVersion({
138 | logger: () => { /* logger */ },
139 | docker: createDockerMock(),
140 | version: '1.1.1',
141 | name: 'myproject',
142 | test_cmd: ['whoami'],
143 | image_name: 'app/image:commit',
144 | path: join(__dirname, 'test_project'),
145 | dockerfile: 'FROM nodejs:$VERSION',
146 | base_image: 'base/image',
147 | verbose: false
148 | })
149 | }
150 | it('Success', (done) => {
151 | docker_mock_run_statuscode = 0
152 | testRun()((err, res) => {
153 | assert.strictEqual(res.success, true)
154 | done(err)
155 | })
156 | })
157 | it('Fail', (done) => {
158 | docker_mock_run_statuscode = 123
159 | testRun()((err, res) => {
160 | assert.strictEqual(res.success, false)
161 | done(err)
162 | })
163 | })
164 | })
165 | })
166 |
--------------------------------------------------------------------------------