├── .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 | Demonstration of functionality 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 | Runs many different examples 100 |
101 |
102 |

autochecker 0.10 5.8

103 |

Only specified versions tested

104 | Runs specified versions only 105 |
106 |
107 |

autochecker 0.10

108 |

Only one specified version tested with verbose output

109 | Runs specified version only 110 |
111 |
112 |

TEST_LIMIT=10 autochecker

113 |

Set the limit of number of parallel tests

114 | Configure how many tests to run in the same time 115 |
116 |
117 |

Other Languages

118 |

Ruby

119 | Example Ruby project with CI integration 120 |

Java, Python, any language

121 | See the examples directory in repository 122 |
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 |
135 |

Documentation

136 | Full documentation is available on Github:
137 | https://github.com/victorbjelkholm/autochecker 138 |

139 |

Created with nothing else but code
(and a bit of coffee) by @VictorBjelkholm

140 |
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 | --------------------------------------------------------------------------------