Repository: jolicode/JoliCi Branch: master Commit: c042812c6ccf Files: 62 Total size: 91.6 KB Directory structure: gitextract_ckyocog0/ ├── .gitignore ├── .gush.yml ├── .pharcc.yml ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin/ │ └── jolici ├── composer.json ├── docs/ │ ├── command/ │ │ ├── clean.md │ │ ├── run.md │ │ └── updates-image.md │ ├── installation.md │ └── strategies/ │ ├── JoliCiStrategy.md │ └── TravisCiStrategy.md ├── phpunit.xml ├── resources/ │ └── templates/ │ ├── Dockerfile-travis.twig │ ├── Dockerfile.twig │ ├── node_js/ │ │ ├── Dockerfile-0.10.twig │ │ ├── Dockerfile-0.11.twig │ │ ├── Dockerfile-0.6.twig │ │ └── Dockerfile-0.8.twig │ ├── php/ │ │ ├── Dockerfile-5.3.twig │ │ ├── Dockerfile-5.4.twig │ │ ├── Dockerfile-5.5.twig │ │ ├── Dockerfile-5.6.twig │ │ ├── Dockerfile-7.twig │ │ ├── Dockerfile-hhvm.twig │ │ └── Dockerfile.twig │ └── ruby/ │ ├── Dockerfile-1.9.3.twig │ ├── Dockerfile-2.0.0.twig │ └── Dockerfile-2.1.0.twig ├── src/ │ └── Joli/ │ └── JoliCi/ │ ├── BuildStrategy/ │ │ ├── BuildStrategyInterface.php │ │ ├── ChainBuildStrategy.php │ │ ├── JoliCiBuildStrategy.php │ │ └── TravisCiBuildStrategy.php │ ├── Builder/ │ │ └── DockerfileBuilder.php │ ├── Command/ │ │ ├── CleanCommand.php │ │ ├── RunCommand.php │ │ └── UpdateImageCommand.php │ ├── Container.php │ ├── Executor.php │ ├── Filesystem/ │ │ └── Filesystem.php │ ├── Job.php │ ├── Log/ │ │ └── SimpleFormatter.php │ ├── LoggerCallback.php │ ├── Matrix.php │ ├── Naming.php │ ├── Service.php │ ├── ServiceManager.php │ └── Vacuum.php └── tests/ └── Joli/ └── JoliCi/ ├── BuildStrategy/ │ ├── ChainBuildStrategyTest.php │ ├── JoliCiBuildStrategyTest.php │ ├── TravisCIBuildStrategyTest.php │ └── fixtures/ │ ├── jolici/ │ │ ├── project1/ │ │ │ ├── .jolici/ │ │ │ │ └── test/ │ │ │ │ └── Dockerfile │ │ │ └── foo │ │ └── project2/ │ │ └── .gitkeep │ └── travisci/ │ └── project1/ │ └── .travis.yml ├── JobTest.php ├── MatrixTest.php └── ServiceTest.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # IDE /.build_dir /.externalTollBuilders /.settings /.project /.buildpath # Dependencies /vendor # Bin /pharcc.phar /jolici.phar ================================================ FILE: .gush.yml ================================================ adapter: github issue_tracker: github ================================================ FILE: .pharcc.yml ================================================ name: jolici.phar main: bin/jolici include: - src/ - vendor/ - resources/ exclude: - "/[Tt]ests?/" - "/docs?/" - "^vendor/phpunit" - "^vendor/bin" ================================================ FILE: .travis.yml ================================================ language: php php: - 5.5 - 5.6 - hhvm - 7.0 matrix: allow_failures: - php: hhvm before_script: - COMPOSER_ROOT_VERSION=dev-master composer --prefer-source install script: ./vendor/bin/phpunit ================================================ FILE: CHANGELOG.md ================================================ ## Change Log ### v0.3.1 (2015/04/02) - Add [images-update command](docs/command/updates-image.md) - Add php7 support - Fix quoted environnement variable #52 - Assumed default versions for each language #51 - Add desktop notifications - Fix output of versbose mode with new lines ### v0.3.0 (2015/02/25) - Better stability with updated docker-php library and error management - Services ! (check the dock) - Cleaning old images and tests ### v0.2.4 (2014/08/01) - [1690410](https://github.com/jolicode/JoliCi/commit/16904105468a718a705376baf9d9ac66bd280c64) Add php 5.6 support (@joelwurtz) - [99a9dea](https://github.com/jolicode/JoliCi/commit/99a9dea3d196845545c21e1da0449865a900d4f1) Remove hack for phpenv, as new images use phpenv (@joelwurtz) - [093a309](https://github.com/jolicode/JoliCi/commit/093a309cb1d804748cde099df618c21967809c48) Add jolici binary to composer. (@nubs) - [d494e90](https://github.com/jolicode/JoliCi/commit/d494e90324f0d87cd6a705b41f825474763a094a) Remove non-available builds from filesystem. (@nubs) - [0a715f8](https://github.com/jolicode/JoliCi/commit/0a715f86cb52fe815f379ff28c4bc03a9b746cf9) Add support for gush (@cordoval) - [9714ab0](https://github.com/jolicode/JoliCi/commit/9714ab02dc5da5b1af78b4bb7ec4881de7c4620a) Use stable version for dependencies (@nubs) ### v0.2.2 (2014/06/26) - [239a2de](https://github.com/jolicode/JoliCi/commit/239a2de6cf5d0db31404dbb39ab6f4b9210aa921) Update dependencies versions (@joelwurtz) ### v0.2.1 (2014/05/15) - [ad1ba9a](https://github.com/jolicode/JoliCi/commit/ad1ba9a3eca7fb4a0e4f3f3e5aba59904d525127) Add ruby versions (@joelwurtz) - [d697ee8](https://github.com/jolicode/JoliCi/commit/d697ee8eedcd0b33b42c2b5e89c918fb222eefbc) Encapsulate command in bash logged with profile support (@joelwurtz) - [c9b86ce](https://github.com/jolicode/JoliCi/commit/c9b86ce4c63ceb430a10930e5a7345e70706a321) Add node js support (@joelwurtz) - [9b1c684](https://github.com/jolicode/JoliCi/commit/9b1c684ebe11991253d76825847d79c91ad92ac8) Add support for env variables (@joelwurtz) - [d1cf3b7](https://github.com/jolicode/JoliCi/commit/d1cf3b7966b385759acc351c299b067b653ac06d) Add timeout support, and set to 5 min by default (@joelwurtz) - [9a551c8](https://github.com/jolicode/JoliCi/commit/9a551c8938f61ef74beaff3737b8dfa7c2091b9e) Add static progress bar when getting image from docker (@joelwurtz) - [f6662c2](https://github.com/jolicode/JoliCi/commit/f6662c25c8ad55ead5b654a75a29668ffa35f236) Make php dockerfile work when using phpenv specific action (like symfony) (@joelwurtz) ### v0.2.0 (2014/02/21) - [7d85d78](https://github.com/jolicode/JoliCi/commit/7d85d78774eaf6a5d41c1cd98a4be4ce7497c054) Adding ruby to travis strategy (@joelwurtz) - [71364c3](https://github.com/jolicode/JoliCi/commit/71364c3814e071cf63983f98933e11ec75fb5ea9) Add a default timeout to 10min for long running build (@joelwurtz) - [7100908](https://github.com/jolicode/JoliCi/commit/7100908c6f78966bd750310b2abfa0af5173f1fb) Refactoring creation of Dockerfile with twig generation (@joelwurtz) ### v0.1.1 (2014/01/18) - [69276ca](https://github.com/jolicode/JoliCi/commit/69276ca8b982b1343519f4119d188a299258c40b) Add travis ci support (@joelwurtz) - [8d469ec](https://github.com/jolicode/JoliCi/commit/8d469ecad0b696f72032e21f0d654ee8ad304c47) Add error management (@joelwurtz) - [945a31a](https://github.com/jolicode/JoliCi/commit/945a31a1750c366eae15f1baeebf1aaff73001ff) Allow to override command when running test (@joelwurtz) ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing First, thank you! Here are a few rules to follow in order to ease code reviews, and discussions before maintainers accept and merge your work. * You **MUST** follow the [PSR-1](http://www.php-fig.org/psr/1/) and [PSR-2](http://www.php-fig.org/psr/2/). * You **MUST** run the test suite. * You **MUST** write (or update) unit tests. * You **SHOULD** write documentation. Please, write [commit messages that make sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), and [rebase your branch](http://git-scm.com/book/en/Git-Branching-Rebasing) before submitting your Pull Request. One may ask you to [squash your commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) too. This is used to "clean" your Pull Request before merging it (we don't want commits such as `fix tests`, `fix 2`, `fix 3`, etc.). Also, when creating your Pull Request on GitHub, you **MUST** write a description which gives the context and/or explains why you are creating it. ================================================ FILE: LICENSE ================================================ Copyright (C) 2014 Joel Wurtz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # JoliCi JoliCi is a free and open source Continuous Integration _Client_ written in PHP (5.4 minimum) and powered by Docker (please use a recent version). It has been written to be compliant with existent Ci services like Travis-Ci and not create a new standard. ([Remove that smile, I KNOW what you're thinking.](http://xkcd.com/927/)) **This project is still in beta, there may be bugs and missing features** [![Build Status](https://travis-ci.org/jolicode/JoliCi.png?branch=master)](https://travis-ci.org/jolicode/JoliCi) [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/jolicode/JoliCi/badges/quality-score.png?s=1ba180546468c07ca8fc0996dcdc4a740dcf23fc)](https://scrutinizer-ci.com/g/jolicode/JoliCi/) ## Usage [Have a .travis.yml in your project](http://docs.travis-ci.com/user/getting-started/#Step-three%3A-Add-.travis.yml-file-to-your-repository) [Download the last version of jolici](https://github.com/jolicode/JoliCi/releases) and run it, i.e. for v0.3.1: ```bash wget https://github.com/jolicode/JoliCi/releases/download/v0.3.1/jolici.phar php jolici.phar run ``` First run can be quite long since it has to build everything from the beginning. Subsequent build should be faster thanks to docker caching. ![JoliCi Demo](https://github.com/jolicode/JoliCi/raw/master/docs/jolici-terminal.gif "JoliCi Demo") If you want to see what happens behind this black box: ```bash php jolici.phar run -v ``` ## Ci supported * Travis-Ci * [...](CONTRIBUTING.md) ## I want to read more * [Installation](docs/installation.md) * Usage * [The run command](docs/command/run.md) * [The clean command](docs/command/clean.md) * [The images-update command](docs/command/updates-image.md) * Strategies (a.k.a. how to create a build from a configuration file) * [Travis-Ci](docs/strategies/TravisCiStrategy.md) * [JoliCi](docs/strategies/JoliCiStrategy.md) ## Credits * [All contributors](https://github.com/jolicode/JoliCi/graphs/contributors) * Some parts of this project are inspired by : * [Docker Client](https://github.com/dotcloud/docker/blob/master/commands.go) * This README is heavily inspired by a @willdurand [blog post](http://williamdurand.fr/2013/07/04/on-open-sourcing-libraries/). * [@ubermuda](https://github.com/ubermuda) for accepting many pull requests on [docker-php](https://github.com/stage1/docker-php) library ## License View the [LICENSE](LICENSE) file attach to this project. ================================================ FILE: bin/jolici ================================================ #!/usr/bin/env php add(new RunCommand()); $application->add(new CleanCommand()); $application->add(new \Joli\JoliCi\Command\UpdateImageCommand()); $application->run(); ================================================ FILE: composer.json ================================================ { "name": "jolicode/jolici", "description": "Run your TravisCi builds locally", "type": "library", "license": "MIT", "keywords": ["test", "ci", "travisci", "docker", "continuous", "integration"], "authors": [ { "name": "Joel Wurtz", "email": "jwurtz@jolicode.com", "homepage": "https://twitter.com/JoelWurtz" } ], "require" : { "php" : ">=5.3", "symfony/console" : "^2.4", "symfony/filesystem" : "^2.4", "symfony/finder" : "^2.4", "symfony/yaml" : "^2.4", "monolog/monolog": "^1.6", "docker-php/docker-php": "^1.22", "cedriclombardot/twig-generator": "^1.0", "guzzlehttp/streams": "^1.3", "behat/transliterator": "^1.0", "jolicode/jolinotif": "^1.0" }, "require-dev": { "phpunit/phpunit": "^4.8", "mikey179/vfsStream": "^1.2" }, "bin": [ "bin/jolici" ], "autoload" : { "psr-0" : { "Joli" : "src" } } } ================================================ FILE: docs/command/clean.md ================================================ # The clean command JoliCi try to keep the number of docker images, containers and build directories as low as possible when running test. But sometimes thing can break hard and cleaning process is not run, so you will need to run this command in order to not overflow your hard drive. ## Default command: ``` php jolici.phar clean ``` The default command will clean all images, containers and build directories except for the last one, in the current project directory. ## Options Here is a list of options you can pass to the clean command: * `--project-path DIRECTORY` / `-p DIRECTORY`: Set the root path (DIRECTORY) of your project, use current directory as a default * `--keep NUMBER` / `-k NUMBER`: How many versions (NUMBER) of images, containers and / or build directories should the clean process keeps * `--only-containers`: Remove only containers * `--only-directories`: Remove only build directories * `--only-images`: Remove only images * `--force`: Force removal of images ================================================ FILE: docs/command/run.md ================================================ # The run command The run command is the main command of JoliCi. It create and prepare the differents environments (jobs) and execute the command on every one. ## Workflow ### Creating environments An environment is created when a build strategy is detected on your project, this can be as simple as [parsing a `.travis.yml`](../strategies/TravisCiStrategy.md) file or more complex by [reading a `.jolici directory](../strategies/JoliCiStrategy.md) Each environment will be prepared in a temporary directory so this will never modify your project (no need to add a line in your .gitignore file) ### Building environment An environment is simply a docker image build with a Dockerfile, which can be generated for you when using TravisCi strategy or using your own with JoliCi strategy ### Starting services Before running test, JoliCi will try to launch services determined by your configuration file (only on TravisCi for the moment), in order to have mysql, memcached, elasticsearch... services available for your tests. Each service will start from a clean state, data is not keeped. For the moment, services are not host in the test container but run in a separate container. To use them you need to set the correct host name for the service on your different configuration files. The hostname for each service is the same as the service name, so i.e. connecting to mysql can be done like that in your test container : ``` mysql -u travis -h mysql ``` Also be aware that services are only available during the script execution, any action using a service before the script will fail (like creating the schema in the install part). Elimination of this two downsides are currently the focus for the next releases. ### Running test Once the environment is ready, JoliCi will run your test command on it and display the output directly on your console. ### Exit code If all test on each environment is successful (return 0 for exit code), jolici will return as well 0 for exit code. Otherwise it will return the number of failing tests. ### Cleaning JoliCi try to do his best to keep disk space as low as possible without decreasing speed. In order to do this it will delete all files related to an environment at the exception of the last run so we can use his cache. ## Default command: ```bash php jolici.phar run ``` The default command will run test for each environments created, this will only output the result of the test command. If you want to see the output of the build process (like TravisCi) you can run this command with a verbose option: ```bash php jolici.phar run -v ``` ## Options Here is a list of options you can pass to the clean command: * `--project-path DIRECTORY` / `-p DIRECTORY`: Set the root path (DIRECTORY) of your project, use current directory as a default * `--keep NUMBER` / `-k NUMBER`: How many versions (NUMBER) of images, containers and / or build directories should be clean after running test (default to 1) * `--no-cache`: Use this option if you don't want to use the cache from the last build * `--timeout TIMEOUT` / `-t TIMEOUT`: This is the timeout, in seconds, for the run command, it allows to aborting test if a command hangs up forever (default to 5 minutes) * `--notify`: Use this option to display desktop notifications for each job ## Overriding test command If one more argument is present there will be considered as the command line for running test, for example to see the php version of an environment created by JoliCi you can do the following command: ```bash php jolici.phar run php -v ``` ================================================ FILE: docs/command/updates-image.md ================================================ # The images-update command This command is used to update all images jolici used to create test environments. ``` $ jolici images-update Update jolicode/php56 image Update jolicode/php55 image Update jolicode/hhvm image Update jolicode/php54 image ``` ================================================ FILE: docs/installation.md ================================================ # Installation ## Install globally ### Download [Download the last version of jolici](https://github.com/jolicode/JoliCi/releases), i.e. for v0.3.1: ```bash curl https://github.com/jolicode/JoliCi/releases/download/v0.3.1/jolici.phar -o /usr/local/bin/jolici chmod +x /usr/local/bin/jolici ``` ```bash wget https://github.com/jolicode/JoliCi/releases/download/v0.3.1/jolici.phar -O /usr/local/bin/jolici chmod +x /usr/local/bin/jolici ``` ### Composer ``` composer global require "jolicode/jolici:*" ``` ## Install per project ### Download ``` curl http://jolici.jolicode.com/jolici.phar ``` ``` wget http://jolici.jolicode.com/jolici.phar ``` ### Composer ``` composer require --dev "jolicode/jolici:*" ``` This tool is mainly used for development purpose so here we set the --dev option, if this tool is not only for developers remove that option. ================================================ FILE: docs/strategies/JoliCiStrategy.md ================================================ # JoliCiBuildStrategy This strategy is based on a directory structure and Dockerfile, this is the most flexible strategy as you can whatever you want for environment. ## Usage To create jobs the project **MUST** have a `.jolici` directory. Each subdirectory is then considered as a different job, they **MUST** have a `Dockerfile` which will explain how to create the environment. The test command is determined by the `CMD` or `ENTRYPOINT` keywords in the `Dockerfile`. ## Overriding files You may want to have a different configuration file for each environment. In order to have this behavior all files under the job directory (`.jolici/my_job_environment`) will be copied, before creation, to the root directory of the project. ## Add source to the build Due to the precedent behavior, the Dockerfile is now at the root of your project. To add the source code of your project in your job environment you can add this command inside your Dockerfile: ``` ADD . /project ``` Your project will then be available at the `/project` path in the environment. ================================================ FILE: docs/strategies/TravisCiStrategy.md ================================================ # TravisCiBuildStrategy This strategy parses the .travis.yml file at the root of your project to create a Dockerfile for each job and run tests. ## Language and version support For the moment only the following language / version list is supported, the goal is to support what travis supports: * php * 5.3 (5.3.28) * 5.4 (5.4.31) * 5.5 (5.5.15) * 5.6 (5.6.3) * hhvm (3.3.0) * ruby * 1.9.3 * 2.0.0 * 2.1.0 * node * 0.6 * 0.8 * 0.10 * 0.11 ## Service support Some services are supported for you to use ## Docker images This strategy use pre-built docker images available on this github repository: [https://github.com/jolicode/docker-images](https://github.com/jolicode/docker-images) ================================================ FILE: phpunit.xml ================================================ tests src ================================================ FILE: resources/templates/Dockerfile-travis.twig ================================================ {% extends "Dockerfile.twig" %} {% block env %} {% for key,value in global_env %} ENV {{ key }}={{ value }} {% endfor %} {% for key,value in env %} ENV {{ key }} {{ value }} {% endfor %} {% endblock %} {% block before_install %} {% for line in before_install %} RUN /bin/bash -c -l "cd $WORKDIR && {{ line|replace({"$": "\\\$", '"': '\\\"'}) }}" {% endfor %} {% endblock %} {% block install %} {% for line in install %} RUN /bin/bash -c -l "cd $WORKDIR && {{ line|replace({"$": "\\\$", '"': '\\\"'}) }}" {% endfor %} {% endblock %} {% block before_script %} {% for line in before_script %} RUN /bin/bash -c -l "cd $WORKDIR && {{ line|replace({"$": "\\\$", '"': '\\\"'}) }}" {% endfor %} {% endblock %} {% block script %} CMD /bin/bash -c -l "cd $WORKDIR{% for line in script %} && {{ line|replace({"$": "\\\$", '"': '\\\"'}) }}{% endfor %}" {% endblock %} ================================================ FILE: resources/templates/Dockerfile.twig ================================================ {% block from %} FROM jolicode/base:latest {% endblock %} {% block content %} {% endblock %} LABEL com.jolici.image="true" ENV WORKDIR $HOME/project ADD . $WORKDIR WORKDIR $HOME/project RUN sudo chown travis:travis -R $HOME/project {% block env %}{% endblock %} {% block before_install %}{% endblock %} {% block install %}{% endblock %} {% block before_script %}{% endblock %} {% block script %}{% endblock %} ================================================ FILE: resources/templates/node_js/Dockerfile-0.10.twig ================================================ {% extends "Dockerfile-travis.twig" %} {% block from %} FROM jolicode/node-0.10:latest {% endblock %} ================================================ FILE: resources/templates/node_js/Dockerfile-0.11.twig ================================================ {% extends "Dockerfile-travis.twig" %} {% block from %} FROM jolicode/node-0.11:latest {% endblock %} ================================================ FILE: resources/templates/node_js/Dockerfile-0.6.twig ================================================ {% extends "Dockerfile-travis.twig" %} {% block from %} FROM jolicode/node-0.6:latest {% endblock %} ================================================ FILE: resources/templates/node_js/Dockerfile-0.8.twig ================================================ {% extends "Dockerfile-travis.twig" %} {% block from %} FROM jolicode/node-0.8:latest {% endblock %} ================================================ FILE: resources/templates/php/Dockerfile-5.3.twig ================================================ {% extends "php/Dockerfile.twig" %} {% block env %} {{ parent() }} ENV TRAVIS_PHP_VERSION php5.3 {% endblock %} {% block from %} FROM jolicode/php53:latest {% endblock %} ================================================ FILE: resources/templates/php/Dockerfile-5.4.twig ================================================ {% extends "php/Dockerfile.twig" %} {% block env %} {{ parent() }} ENV TRAVIS_PHP_VERSION php5.4 {% endblock %} {% block from %} FROM jolicode/php54:latest {% endblock %} ================================================ FILE: resources/templates/php/Dockerfile-5.5.twig ================================================ {% extends "php/Dockerfile.twig" %} {% block env %} {{ parent() }} ENV TRAVIS_PHP_VERSION php5.5 {% endblock %} {% block from %} FROM jolicode/php55:latest {% endblock %} ================================================ FILE: resources/templates/php/Dockerfile-5.6.twig ================================================ {% extends "php/Dockerfile.twig" %} {% block env %} {{ parent() }} ENV TRAVIS_PHP_VERSION php5.6 {% endblock %} {% block from %} FROM jolicode/php56:latest {% endblock %} ================================================ FILE: resources/templates/php/Dockerfile-7.twig ================================================ {% extends "php/Dockerfile.twig" %} {% block env %} {{ parent() }} ENV TRAVIS_PHP_VERSION php7.0-dev {% endblock %} {% block from %} FROM jolicode/php-master:latest {% endblock %} ================================================ FILE: resources/templates/php/Dockerfile-hhvm.twig ================================================ {% extends "Dockerfile-travis.twig" %} {% block from %} FROM jolicode/hhvm:latest {% endblock %} {% block env %} {{ parent() }} ENV TRAVIS_PHP_VERSION hhvm {% endblock %} {% block before_install %} RUN echo 'date.timezone={{ timezone }}' | sudo tee -a /etc/hhvm/php.ini {% endblock %} ================================================ FILE: resources/templates/php/Dockerfile.twig ================================================ {% extends "Dockerfile-travis.twig" %} {% block env %} {{ parent() }} {% endblock %} {% block before_install %} RUN echo 'date.timezone={{ timezone }}' >> /home/.phpenv/versions/`cat /home/.phpenv/version`/etc/php.ini {% endblock %} ================================================ FILE: resources/templates/ruby/Dockerfile-1.9.3.twig ================================================ {% extends "Dockerfile-travis.twig" %} {% block from %} FROM jolicode/ruby-1.9.3:latest {% endblock %} ================================================ FILE: resources/templates/ruby/Dockerfile-2.0.0.twig ================================================ {% extends "Dockerfile-travis.twig" %} {% block from %} FROM jolicode/ruby-2.0.0:latest {% endblock %} ================================================ FILE: resources/templates/ruby/Dockerfile-2.1.0.twig ================================================ {% extends "Dockerfile-travis.twig" %} {% block from %} FROM jolicode/ruby-2.1.0:latest {% endblock %} ================================================ FILE: src/Joli/JoliCi/BuildStrategy/BuildStrategyInterface.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Joli\JoliCi\BuildStrategy; use Joli\JoliCi\Job; /** * Interface that all Build strategy must implement * * @author Joel Wurtz */ interface BuildStrategyInterface { const WORKDIR = "/home/project"; /** * Create / Get jobs for a project * * @param string $directory Location of project * * @return \Joli\JoliCi\Job[] Return a list of jobs to create */ public function getJobs($directory); /** * Prepare a job (generally copy its files to a new directory * * @param \Joli\JoliCi\Job $job * * @return void */ public function prepareJob(Job $job); /** * Return name of the build strategy * * @return string */ public function getName(); /** * Tell if the build strategy is supported for a project * * @param string $directory Location of project * * @return boolean */ public function supportProject($directory); } ================================================ FILE: src/Joli/JoliCi/BuildStrategy/ChainBuildStrategy.php ================================================ strategies[$strategy->getName()] = $strategy; } /** * {@inheritdoc} */ public function getJobs($directory) { $builds = array(); foreach ($this->strategies as $strategy) { if ($strategy->supportProject($directory)) { $builds += $strategy->getJobs($directory); } } return $builds; } /** * {@inheritdoc} */ public function prepareJob(Job $job) { $this->strategies[$job->getStrategy()]->prepareJob($job); } /** * {@inheritdoc} */ public function getName() { return 'Chain'; } /** * {@inheritdoc} */ public function supportProject($directory) { foreach ($this->strategies as $strategy) { if ($strategy->supportProject($directory)) { return true; } } return false; } } ================================================ FILE: src/Joli/JoliCi/BuildStrategy/JoliCiBuildStrategy.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Joli\JoliCi\BuildStrategy; use Joli\JoliCi\Job; use Joli\JoliCi\Filesystem\Filesystem; use Joli\JoliCi\Naming; use Symfony\Component\Finder\Finder; /** * JoliCi implementation for build * * A project must have a .jolici directory, each directory inside this one will be a type of build and must contain a Dockerfile to be executable * * @author Joel Wurtz */ class JoliCiBuildStrategy implements BuildStrategyInterface { /** * @var string Base path for build */ private $buildPath; /** * @var Filesystem Filesystem service */ private $filesystem; /** * @var Naming Use to name the image created */ private $naming; /** * @param string $buildPath Directory where build must be created * @param Naming $naming Naming service * @param Filesystem $filesystem Filesystem service */ public function __construct($buildPath, Naming $naming, Filesystem $filesystem) { $this->buildPath = $buildPath; $this->naming = $naming; $this->filesystem = $filesystem; } /** * {@inheritdoc} */ public function getJobs($directory) { $builds = array(); $finder = new Finder(); $finder->directories(); foreach ($finder->in($this->getJoliCiStrategyDirectory($directory)) as $dir) { $builds[] = new Job( $this->naming->getProjectName($directory), $this->getName(), $this->naming->getUniqueKey(array('build' => $dir->getFilename())), array( 'origin' => $directory, 'build' => $dir->getRealPath(), ), "JoliCi Build: ".$dir->getFilename() ); } return $builds; } /** * {@inheritdoc} */ public function prepareJob(Job $job) { $origin = $job->getParameters()['origin']; $target = $this->buildPath.DIRECTORY_SEPARATOR. $job->getDirectory(); $build = $job->getParameters()['build']; // First mirroring target $this->filesystem->mirror($origin, $target, null, array( 'delete' => true, 'override' => true, )); // Second override target with build dir $this->filesystem->mirror($build, $target, null, array( 'delete' => false, 'override' => true, )); } /** * {@inheritdoc} */ public function getName() { return "JoliCi"; } /** * {@inheritdoc} */ public function supportProject($directory) { return file_exists($this->getJoliCiStrategyDirectory($directory)) && is_dir($this->getJoliCiStrategyDirectory($directory)); } /** * Return the jolici strategy directory where there must be builds * * @param string $projectPath * @return string */ protected function getJoliCiStrategyDirectory($projectPath) { return $projectPath.DIRECTORY_SEPARATOR.'.jolici'; } } ================================================ FILE: src/Joli/JoliCi/BuildStrategy/TravisCiBuildStrategy.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Joli\JoliCi\BuildStrategy; use Joli\JoliCi\Job; use Joli\JoliCi\Builder\DockerfileBuilder; use Joli\JoliCi\Filesystem\Filesystem; use Joli\JoliCi\Matrix; use Joli\JoliCi\Naming; use Joli\JoliCi\Service; use Symfony\Component\Yaml\Yaml; /** * TravisCi implementation for build strategy * * A project must have a .travis.yml file * * @author Joel Wurtz */ class TravisCiBuildStrategy implements BuildStrategyInterface { private $languageVersionKeyMapping = array( 'ruby' => 'rvm', ); private $defaults = array( 'php' => array( 'before_install' => array(), 'install' => array(), 'before_script' => array(), 'script' => array('phpunit'), 'env' => array(), 'default_versions' => array('5.6') ), 'ruby' => array( 'before_install' => array(), 'install' => array(), 'before_script' => array(), 'script' => array('bundle exec rake'), 'env' => array(), 'default_versions' => array('2.1.0') ), 'node_js' => array( 'before_install' => array(), 'install' => array(), 'before_script' => array(), 'script' => array('npm test'), 'env' => array(), 'default_versions' => array('0.10') ), ); private $servicesMapping = array( 'mongodb' => array( 'repository' => 'mongo', 'tag' => '2.6', 'config' => array() ), 'mysql' => array( 'repository' => 'mysql', 'tag' => '5.5', 'config' => array( 'Env' => array( 'MYSQL_ROOT_PASSWORD=""', 'MYSQL_USER=travis', 'MYSQL_PASSWORD=""' ) ) ), 'postgresql' => array( 'repository' => 'postgres', 'tag' => '9.1', 'config' => array() ), 'couchdb' => array( 'repository' => 'fedora/couchdb', 'tag' => 'latest', 'config' => array() ), 'rabbitmq' => array( 'repository' => 'dockerfile/rabbitmq', 'tag' => 'latest', 'config' => array() ), 'memcached' => array( 'repository' => 'sylvainlasnier/memcached', 'tag' => 'latest', 'config' => array() ), 'redis-server' => array( 'repository' => 'redis', 'tag' => '2.8', 'config' => array() ), 'cassandra' => array( 'repository' => 'spotify/cassandra', 'tag' => 'latest', 'config' => array() ), 'neo4j' => array( 'repository' => 'tpires/neo4j', 'tag' => 'latest', 'config' => array() ), 'elasticsearch' => array( 'repository' => 'dockerfile/elasticsearch', 'tag' => 'latest', 'config' => array() ), ); /** * @var DockerfileBuilder Builder for dockerfile */ private $builder; /** * @var string Build path for project */ private $buildPath; /** * @var Filesystem Filesystem service */ private $filesystem; /** * @var \Joli\JoliCi\Naming Naming service to create docker name for images */ private $naming; /** * @param DockerfileBuilder $builder Twig Builder for Dockerfile * @param string $buildPath Directory where builds are created * @param Naming $naming Naming service * @param Filesystem $filesystem Filesystem service */ public function __construct(DockerfileBuilder $builder, $buildPath, Naming $naming, Filesystem $filesystem) { $this->builder = $builder; $this->buildPath = $buildPath; $this->naming = $naming; $this->filesystem = $filesystem; } /** * {@inheritdoc} */ public function getJobs($directory) { $jobs = array(); $config = Yaml::parse(file_get_contents($directory.DIRECTORY_SEPARATOR.".travis.yml")); $matrix = $this->createMatrix($config); $services = $this->getServices($config); $timezone = ini_get('date.timezone'); foreach ($matrix->compute() as $possibility) { $parameters = array( 'language' => $possibility['language'], 'version' => $possibility['version'], 'environment' => $possibility['environment'], ); $description = sprintf('%s = %s', $possibility['language'], $possibility['version']); if ($possibility['environment'] !== null) { $description .= sprintf(', Environment: %s', json_encode($possibility['environment'])); } $jobs[] = new Job($this->naming->getProjectName($directory), $this->getName(), $this->naming->getUniqueKey($parameters), array( 'language' => $possibility['language'], 'version' => $possibility['version'], 'before_install' => $possibility['before_install'], 'install' => $possibility['install'], 'before_script' => $possibility['before_script'], 'script' => $possibility['script'], 'env' => $possibility['environment'], 'global_env' => $possibility['global_env'], 'timezone' => $timezone, 'origin' => realpath($directory), ), $description, null, $services); } return $jobs; } /** * {@inheritdoc} */ public function prepareJob(Job $job) { $parameters = $job->getParameters(); $origin = $parameters['origin']; $target = $this->buildPath.DIRECTORY_SEPARATOR. $job->getDirectory(); // First mirroring target $this->filesystem->mirror($origin, $target, null, array( 'delete' => true, 'override' => true, )); // Create dockerfile $this->builder->setTemplateName(sprintf("%s/Dockerfile-%s.twig", $parameters['language'], $parameters['version'])); $this->builder->setVariables($parameters); $this->builder->setOutputName('Dockerfile'); $this->builder->writeOnDisk($target); } /** * {@inheritdoc} */ public function getName() { return "TravisCi"; } /** * {@inheritdoc} */ public function supportProject($directory) { return file_exists($directory.DIRECTORY_SEPARATOR.".travis.yml") && is_file($directory.DIRECTORY_SEPARATOR.".travis.yml"); } /** * Get command lines to add for a configuration value in .travis.yml file * * @param array $config Configuration of travis ci parsed * @param string $language Language for getting the default value if no value is set * @param string $key Configuration key * * @return array A list of command to add to Dockerfile */ private function getConfigValue($config, $language, $key) { if (!isset($config[$key]) || empty($config[$key])) { if (isset($this->defaults[$language][$key])) { return $this->defaults[$language][$key]; } return array(); } if (!is_array($config[$key])) { return array($config[$key]); } return $config[$key]; } /** * Create matrix of build * * @param array $config * * @return Matrix */ protected function createMatrix($config) { $language = isset($config['language']) ? $config['language'] : 'ruby'; if (!isset($this->defaults[$language])) { throw new \Exception(sprintf('Language %s not supported', $language)); } $versionKey = isset($this->languageVersionKeyMapping[$language]) ? $this->languageVersionKeyMapping[$language] : $language; $environmentLines = $this->getConfigValue($config, $language, "env"); $environnements = array(); $globalEnv = array(); $matrixEnv = $environmentLines; $versions = (array) (isset($config[$versionKey]) ? $config[$versionKey] : $this->defaults[$language]['default_versions']); foreach ($versions as $key => $version) { if (!$this->isLanguageVersionSupported($language, $version)) { unset($versions[$key]); } } if (isset($environmentLines['matrix'])) { $matrixEnv = $environmentLines['matrix']; } if (isset($environmentLines['global'])) { foreach ($environmentLines['global'] as $environementVariable) { if (is_array($environementVariable) && array_key_exists('secure', $environementVariable)) { continue; } list ($key, $value) = $this->parseEnvironementVariable($environementVariable); $globalEnv = array_merge($globalEnv, array($key => $value)); } if (!isset($environmentLines['matrix'])) { $matrixEnv = array(); } } // Parsing environnements foreach ($matrixEnv as $environmentLine) { $environnements[] = $this->parseEnvironmentLine($environmentLine); } $matrix = new Matrix(); $matrix->setDimension('language', array($language)); $matrix->setDimension('environment', $environnements); $matrix->setDimension('global_env', array($globalEnv)); $matrix->setDimension('version', $versions); $matrix->setDimension('before_install', array($this->getConfigValue($config, $language, 'before_install'))); $matrix->setDimension('install', array($this->getConfigValue($config, $language, 'install'))); $matrix->setDimension('before_script', array($this->getConfigValue($config, $language, 'before_script'))); $matrix->setDimension('script', array($this->getConfigValue($config, $language, 'script'))); return $matrix; } /** * Get services list from travis ci configuration file * * @param $config * * @return Service[] */ protected function getServices($config) { $services = array(); $travisServices = isset($config['services']) && is_array($config['services']) ? $config['services'] : array(); foreach ($travisServices as $service) { if (isset($this->servicesMapping[$service])) { $services[] = new Service( $service, $this->servicesMapping[$service]['repository'], $this->servicesMapping[$service]['tag'], $this->servicesMapping[$service]['config'] ); } } return $services; } /** * Parse an environnement line from Travis to return an array of variables * * Transform: * "A=B C=D" * Into: * array('a' => 'b', 'c' => 'd') * * @param $environmentLine * @return array */ private function parseEnvironmentLine($environmentLine) { $variables = array();@ $variableLines = explode(' ', $environmentLine ?: ''); foreach ($variableLines as $variableLine) { if (!empty($variableLine)) { list($key, $value) = $this->parseEnvironementVariable($variableLine); $variables[$key] = $value; } } return $variables; } /** * Parse an envar * * @param $envVar * @return array */ private function parseEnvironementVariable($envVar) { return explode('=', $envVar); } private function isLanguageVersionSupported($language, $version) { return file_exists(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . 'Dockerfile-' . $version . '.twig'); } } ================================================ FILE: src/Joli/JoliCi/Builder/DockerfileBuilder.php ================================================ setName('clean'); $this->setDescription('Clean images, containers and/or directories of previous build for this project'); $this->addOption('project-path', 'p', InputOption::VALUE_OPTIONAL, "Path where you project is (default to current directory)", "."); $this->addOption('keep', 'k', InputOption::VALUE_OPTIONAL, "Number of images / containers / directories per build to keep", 1); $this->addOption('only-containers', null, InputOption::VALUE_NONE, "Only clean containers (no images or directories)"); $this->addOption('only-directories', null, InputOption::VALUE_NONE, "Only clean directories (no images or containers)"); $this->addOption('only-images', null, InputOption::VALUE_NONE, "Only clean images (no containers or directories), be aware that this may fail if containers are still attached to images (you may need to use force option)"); $this->addOption('force', null, InputOption::VALUE_NONE, "Force removal for images"); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $container = new Container(); $vacuum = $container->getVacuum(); if ($input->getOption('only-containers')) { $vacuum->cleanContainers($vacuum->getJobsToRemove($input->getOption('project-path'), $input->getOption('keep'))); return 0; } if ($input->getOption('only-directories')) { $vacuum->cleanDirectories($vacuum->getJobsToRemove($input->getOption('project-path'), $input->getOption('keep'))); return 0; } if ($input->getOption('only-images')) { $vacuum->cleanImages($vacuum->getJobsToRemove($input->getOption('project-path'), $input->getOption('keep')), $input->getOption('force')); return 0; } $vacuum->clean($input->getOption('project-path'), $input->getOption('keep'), $input->getOption('force')); return 0; } } ================================================ FILE: src/Joli/JoliCi/Command/RunCommand.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Joli\JoliCi\Command; use Joli\JoliCi\Container; use Joli\JoliNotif\Notification; use Joli\JoliNotif\NotifierFactory; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; class RunCommand extends Command { /** * {@inheritdoc} */ protected function configure() { $this->setName('run'); $this->setDescription('Run tests on your project'); $this->addOption('project-path', 'p', InputOption::VALUE_OPTIONAL, "Path where you project is (default to current directory)", "."); $this->addOption('keep', 'k', InputOption::VALUE_OPTIONAL, "Number of images / containers / directories per build to keep when cleaning at the end of run", 1); $this->addOption('no-cache', null, InputOption::VALUE_NONE, "Do not use cache of docker"); $this->addOption('timeout', null, InputOption::VALUE_OPTIONAL, "Timeout for docker client in seconds (default to 5 minutes)", "300"); $this->addOption('notify', null, InputOption::VALUE_NONE, "Show desktop notifications when a build is finished"); $this->addArgument('cmd', InputArgument::OPTIONAL, "Override test command"); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $container = new Container(); $verbose = (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()); $strategy = $container->getChainStrategy(); $executor = $container->getExecutor(!$input->getOption('no-cache'), $verbose, $input->getOption('timeout')); $serviceManager = $container->getServiceManager($verbose); $notifier = NotifierFactory::create(); $output->writeln("Creating builds..."); $jobs = $strategy->getJobs($input->getOption("project-path")); $output->writeln(sprintf("%s builds created", count($jobs))); $exitCode = 0; try { foreach ($jobs as $job) { $output->writeln(sprintf("\nRunning job %s\n", $job->getDescription())); $serviceManager->start($job); $strategy->prepareJob($job); $success = $executor->test($job, $input->getArgument('cmd')); $exitCode += $success == 0 ? 0 : 1; if ($input->getOption('notify')) { $notification = new Notification(); $notification->setBody(sprintf('Test results for %s on %s', $container->getNaming()->getProjectName($input->getOption('project-path')), $job->getDescription())); $notification->setTitle($success == 0 ? 'Tests passed' : 'Tests failed'); $notifier->send($notification); } $serviceManager->stop($job); } } catch (\Exception $e) { // Try stop last builds if (isset($job)) { $serviceManager->stop($job); } // We do not deal with exception (Console Component do it well), // we just catch it to allow cleaner to be runned even if one of the build failed hard // Simulation of a finally for php < 5.6 :-° } $container->getVacuum()->clean($input->getOption("project-path"), $input->getOption("keep")); if (isset($e)) { throw $e; } return $exitCode; } } ================================================ FILE: src/Joli/JoliCi/Command/UpdateImageCommand.php ================================================ setName('images-update'); $this->setDescription('Update docker images used to build test environnement'); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $container = new Container(); $docker = $container->getDocker(); $logger = $container->getLoggerCallback((OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity())); foreach ($docker->getImageManager()->findAll() as $image) { if (preg_match('#^jolicode/(.+?)$#', $image->getRepository())) { $output->writeln(sprintf("Update %s image", $image->getRepository())); $docker->getImageManager()->pull($image->getRepository(), 'latest', $logger->getBuildCallback()); } } } } ================================================ FILE: src/Joli/JoliCi/Container.php ================================================ setTemplateDirs(array( __DIR__."/../../../resources/templates", )); $generator->setMustOverwriteIfExists(true); $generator->addBuilder($builder); return new TravisCiBuildStrategy($builder, $this->getBuildPath(), $this->getNaming(), $this->getFilesystem()); } /** * Strategy based on the ".jolici" folder * * @return JoliCiBuildStrategy */ public function getJoliCiStrategy() { return new JoliCiBuildStrategy($this->getBuildPath(), $this->getNaming(), $this->getFilesystem()); } /** * Chain strategy to allow multiples ones * * @return ChainBuildStrategy */ public function getChainStrategy() { $strategy = new ChainBuildStrategy(); $strategy->pushStrategy($this->getTravisCiStrategy()); $strategy->pushStrategy($this->getJoliCiStrategy()); return $strategy; } /** * Alias for the main strategy * * @return \Joli\JoliCi\BuildStrategy\BuildStrategyInterface */ public function getStrategy() { return $this->getChainStrategy(); } /** * Get a console with finger crossed handler * * @param bool $verbose * * @return Logger */ public function getConsoleLogger($verbose = false) { $logger = new Logger("standalone-logger"); $handler = new StreamHandler("php://stdout", $verbose ? Logger::DEBUG : Logger::INFO); $simpleFormatter = new SimpleFormatter(); $handler->setFormatter($simpleFormatter); $logger->pushHandler($handler); if (!$verbose) { $stdErrHandler = new StreamHandler("php://stderr", Logger::DEBUG); $fingerCrossedHandler = new FingersCrossedHandler($stdErrHandler, new ErrorLevelActivationStrategy(Logger::ERROR), 10); $logger->pushHandler($fingerCrossedHandler); $stdErrHandler->setFormatter($simpleFormatter); } return $logger; } public function getVacuum() { return new Vacuum($this->getDocker(), $this->getNaming(), $this->getStrategy(), $this->getFilesystem(), $this->getBuildPath()); } public function getFilesystem() { return new Filesystem(); } public function getDocker() { if (!$this->docker) { $this->docker = new Docker(); } return $this->docker; } public function getExecutor($cache = true, $verbose = false, $timeout = 600) { return new Executor($this->getLoggerCallback($verbose), $this->getDocker(), $this->getBuildPath(), $cache, false, $timeout); } public function getServiceManager($verbose = false) { return new ServiceManager($this->getDocker(), $this->getLoggerCallback($verbose)); } public function getBuildPath() { return sys_get_temp_dir().DIRECTORY_SEPARATOR.".jolici-builds"; } public function getNaming() { return new Naming(); } public function getLoggerCallback($verbose) { return new LoggerCallback($this->getConsoleLogger($verbose)); } } ================================================ FILE: src/Joli/JoliCi/Executor.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Joli\JoliCi; use Docker\API\Model\BuildInfo; use Docker\API\Model\ContainerConfig; use Docker\API\Model\HostConfig; use Docker\Docker; use Docker\Context\Context; use Docker\Manager\ContainerManager; use Docker\Manager\ImageManager; use Docker\Stream\BuildStream; use Http\Client\Plugin\Exception\ClientErrorException; use Monolog\Logger; class Executor { /** * Docker client * * @var Docker */ protected $docker; /** * Logger to log message when building * * @var LoggerCallback */ protected $logger; /** * @var boolean Use cache when building */ private $usecache = true; /** * @var boolean Use cache when building */ private $quietBuild = true; /** * @var integer Default timeout for run */ private $timeout = 600; /** * @var string Base directory where builds are located */ private $buildPath; public function __construct(LoggerCallback $logger, Docker $docker, $buildPath, $usecache = true, $quietBuild = true, $timeout = 600) { $this->logger = $logger; $this->docker = $docker; $this->usecache = $usecache; $this->quietBuild = $quietBuild; $this->timeout = $timeout; $this->buildPath = $buildPath; } /** * Test a build * * @param Job $build * @param array|string $command * * @return integer */ public function test(Job $build, $command = null) { $exitCode = 1; if (false !== $this->create($build)) { $exitCode = $this->run($build, $command); } return $exitCode; } /** * Create a build * * @param Job $job Build used to create image * * @return \Docker\API\Model\Image|boolean Return the image created if successful or false otherwise */ public function create(Job $job) { $context = new Context($this->buildPath . DIRECTORY_SEPARATOR . $job->getDirectory()); $buildStream = $this->docker->getImageManager()->build($context->toStream(), [ 't' => $job->getName(), 'q' => $this->quietBuild, 'nocache' => !$this->usecache ], ImageManager::FETCH_STREAM); $buildStream->onFrame($this->logger->getBuildCallback()); $buildStream->wait(); try { return $this->docker->getImageManager()->find($job->getName()); } catch (ClientErrorException $e) { if ($e->getResponse()->getStatusCode() == 404) { return false; } throw $e; } } /** * Run a build (it's suppose the image exist in docker * * @param Job $job Build to run * @param string|array $command Command to use when run the build (null, by default, will use the command registered to the image) * * @return integer The exit code of the command run inside (0 = success, otherwise it has failed) */ public function run(Job $job, $command) { if (is_string($command)) { $command = ['/bin/bash', '-c', $command]; } $image = $this->docker->getImageManager()->find($job->getName()); $hostConfig = new HostConfig(); $config = new ContainerConfig(); $config->setCmd($command); $config->setImage($image->getId()); $config->setHostConfig($hostConfig); $config->setLabels(new \ArrayObject([ 'com.jolici.container=true' ])); $config->setAttachStderr(true); $config->setAttachStdout(true); $links = []; foreach ($job->getServices() as $service) { if ($service->getContainer()) { $serviceContainer = $this->docker->getContainerManager()->find($service->getContainer()); $links[] = sprintf('%s:%s', $serviceContainer->getName(), $service->getName()); } } $hostConfig->setLinks($links); $containerCreateResult = $this->docker->getContainerManager()->create($config); $attachStream = $this->docker->getContainerManager()->attach($containerCreateResult->getId(), [ 'stream' => true, 'stdout' => true, 'stderr' => true, ], ContainerManager::FETCH_STREAM); $attachStream->onStdout($this->logger->getRunStdoutCallback()); $attachStream->onStderr($this->logger->getRunStderrCallback()); $this->docker->getContainerManager()->start($containerCreateResult->getId()); $attachStream->wait(); $containerWait = $this->docker->getContainerManager()->wait($containerCreateResult->getId()); return $containerWait->getStatusCode(); } } ================================================ FILE: src/Joli/JoliCi/Filesystem/Filesystem.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Joli\JoliCi\Filesystem; use Symfony\Component\Filesystem\Filesystem as BaseFilesystem; class Filesystem extends BaseFilesystem { /** * Add keeping same permissions as origin file * * @see \Symfony\Component\Filesystem\Filesystem::copy() * * @param string $originFile * @param string $targetFile * @param Boolean $override */ public function copy($originFile, $targetFile, $override = false) { parent::copy($originFile, $targetFile, $override); $this->chmod($targetFile, fileperms($originFile)); } } ================================================ FILE: src/Joli/JoliCi/Job.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Joli\JoliCi; class Job { const BASE_NAME = 'jolici'; /** * @var string Name of job */ protected $project; /** * @var string Strategy associated with this job */ protected $strategy; /** * @var string Uniq key for this "kind" of job * * This key is not a identifier (the name is the identifier in a job), it's more like a category, * job with same parameters MUST have the same uniq key, this key is use to track the history * of a job over time (for cleaning, reports, etc ....) */ protected $uniq; /** * @var array Parameters of this job * * It mainly depend on the strategy, ie for TravisCi strategy this will include the language used, version used, etc ... */ protected $parameters; /** * @var string Description of this job (generally a nice name for end user) */ protected $description; /** * @var \DateTime Date of creation of the job */ protected $created; /** * @var Service[] Services linked to this job */ private $services = array(); /** * @param string $project Project of the job * @param string $strategy Strategy of the job * @param string $uniq A uniq identifier for this kind of job * @param array $parameters Parameters of the job (mainly depend on the strategy) * @param string $description Description of this job (generally a nice name for end user) * @param \DateTime $created Date of creation of the job * @param array $services Services linked to the job */ public function __construct($project, $strategy, $uniq, $parameters = array(), $description = "", $created = null, $services = array()) { $this->project = $project; $this->description = $description; $this->strategy = $strategy; $this->parameters = $parameters; $this->uniq = $uniq; if (null === $created) { $created = new \DateTime(); } $this->created = $created; $this->services = $services; } /** * Get name of this job * * @return string */ public function getName() { return sprintf('%s:%s', $this->getRepository(), $this->getTag()); } /** * Get repository name for docker images job with this strategy * * @return string */ public function getRepository() { return sprintf('%s_%s/%s', static::BASE_NAME, strtolower($this->strategy), $this->project); } /** * Generate the tag name for a docker image * * @return string */ public function getTag() { return sprintf('%s-%s', $this->uniq, $this->created->format('U')); } /** * Add a service to the job * * @param Service $service */ public function addService(Service $service) { $this->services[] = $service; } /** * Return all services linked to this job * * @return Service[] */ public function getServices() { return $this->services; } /** * Return directory of job * * @return string */ public function getDirectory() { return $this->getName(); } /** * @return string */ public function getDescription() { return $this->description; } /** * @return string */ public function getStrategy() { return $this->strategy; } /** * @return string */ public function getUniq() { return $this->uniq; } /** * @return array */ public function getParameters() { return $this->parameters; } /** * @return \DateTime */ public function getCreated() { return $this->created; } public function __toString() { return $this->getName(); } } ================================================ FILE: src/Joli/JoliCi/Log/SimpleFormatter.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Joli\JoliCi\Log; use Monolog\Formatter\FormatterInterface; class SimpleFormatter implements FormatterInterface { private $static = array(); private $lineNumber = 0; /* * (non-PHPdoc) @see \Monolog\Formatter\FormatterInterface::format() */ public function format(array $record) { if (isset($record['context']['clear-static'])) { $this->static = array(); return ""; } $message = $record['message']; if (isset($record['context']['static']) && $record['context']['static']) { $id = $record['context']['static-id']; if (!isset($this->static[$id])) { $this->static[$id] = array( 'current_line' => $this->lineNumber, 'message' => $message, ); $message = sprintf("%s\n", $message); } else { $diff = ($this->lineNumber - $this->static[$id]['current_line']); $lastMessage = $this->static[$id]['message']; $this->static[$id]['message'] = $message; //Add space to replace old string in message if (mb_strlen($lastMessage) > mb_strlen($message)) { $message = str_pad($message, mb_strlen($lastMessage), " ", STR_PAD_RIGHT); } $message = sprintf("\x0D\x1B[%sA%s\x1B[%sB\x0D", $diff, $message, $diff); } } if (preg_match('#\n#', $message)) { $this->lineNumber++; } return $message; } /* * (non-PHPdoc) @see \Monolog\Formatter\FormatterInterface::formatBatch() */ public function formatBatch(array $records) { $message = ''; foreach ($records as $record) { $message .= $this->format($record); } return $message; } } ================================================ FILE: src/Joli/JoliCi/LoggerCallback.php ================================================ buildCallback = $build->getClosure($this); $this->runStdoutCallback = $runStdout->getClosure($this); $this->runStderrCallback = $runStderr->getClosure($this); $this->logger = $logger; } /** * Get the build log callback when building / pulling an image * * @return callable */ public function getBuildCallback() { return $this->buildCallback; } /** * Get the run stdout callback for docker * * @return callable */ public function getRunStdoutCallback() { return $this->runStdoutCallback; } /** * Get the run stderr callback for docker * * @return callable */ public function getRunStderrCallback() { return $this->runStderrCallback; } /** * Clear static log buffer */ public function clearStatic() { $this->logger->debug("", array('clear-static' => true)); } /** * The build callback when creating a image, useful to see what happens during building * * @param BuildInfo $output An encoded json string from docker daemon */ private function buildCallback(BuildInfo $output) { $message = ""; if ($output->getError()) { $this->logger->error(sprintf("Error when creating job: %s\n", $output->getError()), array('static' => false, 'static-id' => null)); return; } if ($output->getStream()) { $message = $output->getStream(); } if ($output->getStatus()) { $message = $output->getStatus(); if ($output->getProgress()) { $message .= " " . $output->getProgress(); } } // Force new line if (!$output->getId() && !preg_match('#\n#', $message)) { $message .= "\n"; } $this->logger->debug($message, array( 'static' => $output->getId() !== null, 'static-id' => $output->getId(), )); } /** * Run callback to catch stdout logs of test running * * @param string $output Output from run (stdout) */ private function runStdoutCallback($output) { $this->logger->info($output); } /** * Run callback to catch stderr logs of test running * * @param string $output Output from run (stderr) */ private function runStderrCallback($output) { $this->logger->error($output); } } ================================================ FILE: src/Joli/JoliCi/Matrix.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Joli\JoliCi; /** * Create the Job list by computing all available possibility through dimensions */ class Matrix { private $dimensions = array(); /** * Set a dimension for this matrix * * @param string $name Name of the dimension * @param array $values Value for this dimension */ public function setDimension($name, array $values) { if (empty($values)) { $values = array(null); } $this->dimensions[$name] = $values; } /** * Return all possibility for the matrix * * @return array */ public function compute() { $dimensions = $this->dimensions; if (empty($dimensions)) { return array(); } // Pop first dimension $values = reset($dimensions); $name = key($dimensions); unset($dimensions[$name]); // Create all possiblites for the first dimension $posibilities = array(); foreach ($values as $v) { $posibilities[] = array($name => $v); } // If only one dimension return simple all the possibilites created (break point of recursivity) if (empty($dimensions)) { return $posibilities; } // If not create a new matrix with remaining dimension $matrix = new Matrix(); foreach ($dimensions as $name => $values) { $matrix->setDimension($name, $values); } $result = $matrix->compute(); $newResult = array(); foreach ($result as $value) { foreach ($posibilities as $possiblity) { $newResult[] = $value + $possiblity; } } return $newResult; } } ================================================ FILE: src/Joli/JoliCi/Naming.php ================================================ name = $name; $this->repository = $repository; $this->tag = $tag; $this->config = $config; } /** * @return string */ public function getName() { return $this->name; } /** * @return string */ public function getRepository() { return $this->repository; } /** * @return string */ public function getTag() { return $this->tag; } /** * @return array */ public function getConfig() { return $this->config; } /** * @return string The container id */ public function getContainer() { return $this->container; } /** * @param string $container The container id */ public function setContainer($container) { $this->container = $container; } } ================================================ FILE: src/Joli/JoliCi/ServiceManager.php ================================================ docker = $docker; $this->logger = $logger; } /** * Start services for a Job * * @param Job $build */ public function start(Job $build) { foreach ($build->getServices() as $service) { try { $this->docker->getImageManager()->find(sprintf('%s:%s', $service->getRepository(), $service->getTag())); } catch (ClientErrorException $e) { if ($e->getResponse()->getStatusCode() == 404) { $buildStream = $this->docker->getImageManager()->create(null, [ 'fromImage' => sprintf('%s:%s', $service->getRepository(), $service->getTag()) ], ImageManager::FETCH_STREAM); $buildStream->onFrame($this->logger->getBuildCallback()); $buildStream->wait(); } else { throw $e; } } $serviceConfig = $service->getConfig(); $containerConfig = new ContainerConfig(); $containerConfig->setImage(sprintf('%s:%s', $service->getRepository(), $service->getTag())); $containerConfig->setLabels([ 'com.jolici.container=true' ]); if (isset($serviceConfig['Env'])) { $containerConfig->setEnv($serviceConfig['Env']); } $containerCreateResult = $this->docker->getContainerManager()->create($containerConfig); $this->docker->getContainerManager()->start($containerCreateResult->getId()); $service->setContainer($containerCreateResult->getId()); } } /** * Stop services for a Job and reinit volumes * * @param Job $job The job to stop services * @param int $timeout Timeout to wait before killing the service */ public function stop(Job $job, $timeout = 10) { foreach ($job->getServices() as $service) { if ($service->getContainer()) { try { $this->docker->getContainerManager()->stop($service->getContainer(), [ 't' => $timeout ]); } catch (ClientErrorException $e) { if ($e->getResponse()->getStatusCode() != 304) { throw $e; } } $this->docker->getContainerManager()->remove($service->getContainer(), [ 'v' => true, 'force' => true ]); $service->setContainer(null); } } } } ================================================ FILE: src/Joli/JoliCi/Vacuum.php ================================================ docker = $docker; $this->naming = $naming; $this->strategy = $strategy; $this->buildPath = $buildPath; $this->filesystem = $filesystem; } /** * Clean containers, images and directory from a project * * @param string $projectPath Location of the project * @param int $keep How many versions does we need to keep (1 is the default in order to have cache for each test) * @param boolean $force Force removal for images */ public function clean($projectPath, $keep = 1, $force = false) { $builds = $this->getJobsToRemove($projectPath, $keep); $this->cleanDirectories($builds); $this->cleanContainers($builds); $this->cleanImages($builds, $force); } /** * Clean directories for given builds * * @param \Joli\JoliCi\Job[] $jobs A list of jobs to remove images from */ public function cleanDirectories($jobs = array()) { foreach ($jobs as $job) { $this->filesystem->remove($job->getDirectory()); } } /** * Clean images for given builds * * @param \Joli\JoliCi\Job[] $jobs A list of jobs to remove images from */ public function cleanContainers($jobs = array()) { $images = array(); $containers = array(); foreach ($jobs as $job) { if (isset($job->getParameters()['image'])) { $images[] = $job->getParameters()['image']; } else { $images[] = sprintf('%s:%s', $job->getRepository(), $job->getTag()); } } foreach ($this->docker->getContainerManager()->findAll(['all' => 1]) as $container) { $imageName = $container->getImage(); foreach ($images as $image) { if ($image == $imageName) { $containers[] = $container; } if (preg_match('#^'.$imageName.'#', $image->getId())) { $containers[] = $container; } } } foreach ($containers as $container) { $this->docker->getContainerManager()->remove($container->getId(), [ 'v' => true, 'force' => true ]); } } /** * Clean images for given builds * * @param \Joli\JoliCi\Job[] $jobs A list of jobs to remove images from * @param boolean $force Force removal for images */ public function cleanImages($jobs = array(), $force = false) { foreach ($jobs as $job) { $this->docker->getImageManager()->remove(sprintf('%s:%s', $job->getRepository(), $job->getTag()), [ 'force' => $force ]); } } /** * Get all jobs to remove given a project and how many versions to keep * * @param string $projectPath The project path * @param int $keep Number of project to keep * * @return \Joli\JoliCi\Job[] A list of jobs to remove */ public function getJobsToRemove($projectPath, $keep = 1) { $currentJobs = $this->strategy->getJobs($projectPath); $existingJobs = $this->getJobs($projectPath); $uniqList = array(); $removes = array(); $ordered = array(); foreach ($currentJobs as $job) { $uniqList[] = $job->getUniq(); } // Remove not existant image (old build configuration) foreach ($existingJobs as $job) { if (!in_array($job->getUniq(), $uniqList)) { $removes[] = $job; } else { $ordered[$job->getUniq()][$job->getCreated()->format('U')] = $job; } } // Remove old image foreach ($ordered as $jobs) { ksort($jobs); $keeped = count($jobs); while ($keeped > $keep) { $removes[] = array_shift($jobs); $keeped--; } } return $removes; } /** * Get all jobs related to a project * * @param string $projectPath Directory where the project is * * @return \Joli\JoliCi\Job[] */ protected function getJobs($projectPath) { $jobs = array(); $project = $this->naming->getProjectName($projectPath); $repositoryRegex = sprintf('#^%s_([a-z]+?)/%s:\d+-\d+$#', Job::BASE_NAME, $project); foreach ($this->docker->getImageManager()->findAll() as $image) { foreach ($image->getRepoTags() as $name) { if (preg_match($repositoryRegex, $name, $matches)) { $jobs[] = $this->getJobFromImage($image, $name, $matches[1], $project); } } } return $jobs; } /** * Create a job from a docker image * * @param ImageItem $image * @param string $strategy * @param string $project * * @return \Joli\JoliCi\Job */ protected function getJobFromImage(ImageItem $image, $imageName, $strategy, $project) { $tag = explode(':', $imageName)[1]; list($uniq, $timestamp) = explode('-', $tag); return new Job($project, $strategy, $uniq, array('image' => $image), "", \DateTime::createFromFormat('U', $timestamp)); } } ================================================ FILE: tests/Joli/JoliCi/BuildStrategy/ChainBuildStrategyTest.php ================================================ pushStrategy(new FooBuildStrategy()); $jobs = $builder->getJobs("test"); $this->assertCount(1, $jobs); $job = $jobs[0]; $this->assertEquals("test", $job->getStrategy()); $this->assertContains("jolici_test/test:test-", $job->getName()); $this->assertContains("jolici_test/test:test-", $job->getDirectory()); } public function testNoJobsEmpty() { $builder = new ChainBuildStrategy(); $builder->pushStrategy(new NoBuildStrategy()); $jobs = $builder->getJobs("test"); $this->assertCount(0, $jobs); } } class FooBuildStrategy implements BuildStrategyInterface { public function getJobs($directory) { return array(new Job("test", "test", "test")); } public function getName() { return "dummy"; } public function prepareJob(Job $job) { } public function supportProject($directory) { return true; } } class NoBuildStrategy extends FooBuildStrategy { public function supportProject($directory) { return false; } } ================================================ FILE: tests/Joli/JoliCi/BuildStrategy/JoliCiBuildStrategyTest.php ================================================ buildPath = vfsStream::setup('build-path'); $this->strategy = new JoliCiBuildStrategy(vfsStream::url('build-path'), new Naming(), new Filesystem()); } public function testCreateJobs() { $jobs = $this->strategy->getJobs(__DIR__.DIRECTORY_SEPARATOR."fixtures".DIRECTORY_SEPARATOR."jolici".DIRECTORY_SEPARATOR."project1"); $this->assertCount(1, $jobs); } public function testPrepareJob() { $jobs = $this->strategy->getJobs(__DIR__.DIRECTORY_SEPARATOR."fixtures".DIRECTORY_SEPARATOR."jolici".DIRECTORY_SEPARATOR."project1"); $job = $jobs[0]; $this->strategy->prepareJob($job); $this->assertTrue($this->buildPath->hasChild(vfsStream::path(vfsStream::url('build-path') . DIRECTORY_SEPARATOR . $job->getDirectory()))); $this->assertTrue($this->buildPath->hasChild(vfsStream::path(vfsStream::url('build-path') . DIRECTORY_SEPARATOR . $job->getDirectory())."/Dockerfile")); $this->assertTrue($this->buildPath->hasChild(vfsStream::path(vfsStream::url('build-path') . DIRECTORY_SEPARATOR . $job->getDirectory())."/foo")); $this->assertTrue($this->buildPath->hasChild(vfsStream::path(vfsStream::url('build-path') . DIRECTORY_SEPARATOR . $job->getDirectory())."/.jolici")); $this->assertTrue($this->buildPath->hasChild(vfsStream::path(vfsStream::url('build-path') . DIRECTORY_SEPARATOR . $job->getDirectory())."/.jolici/test")); $this->assertTrue($this->buildPath->hasChild(vfsStream::path(vfsStream::url('build-path') . DIRECTORY_SEPARATOR . $job->getDirectory())."/.jolici/test/Dockerfile")); } public function testSupportTrue() { $support = $this->strategy->supportProject(__DIR__.DIRECTORY_SEPARATOR."fixtures".DIRECTORY_SEPARATOR."jolici".DIRECTORY_SEPARATOR."project1"); $this->assertTrue($support); } public function testSupportFalse() { $support = $this->strategy->supportProject(__DIR__.DIRECTORY_SEPARATOR."fixtures".DIRECTORY_SEPARATOR."jolici".DIRECTORY_SEPARATOR."project2"); $this->assertFalse($support); } } ================================================ FILE: tests/Joli/JoliCi/BuildStrategy/TravisCIBuildStrategyTest.php ================================================ buildPath = vfsStream::setup('build-path'); $this->strategy = new TravisCiBuildStrategy(new DockerfileBuilder(), vfsStream::url('build-path'), new Naming(), new Filesystem()); } public function testSupportTrue() { $support = $this->strategy->supportProject(__DIR__.DIRECTORY_SEPARATOR."fixtures".DIRECTORY_SEPARATOR."travisci".DIRECTORY_SEPARATOR."project1"); $this->assertTrue($support); } public function testSupportFalse() { $support = $this->strategy->supportProject(__DIR__.DIRECTORY_SEPARATOR."fixtures".DIRECTORY_SEPARATOR."travisci".DIRECTORY_SEPARATOR."project2"); $this->assertFalse($support); } /** * @dataProvider createMatrixVersionDataProvider */ public function testCreateMatrixCanObtainVersions($versions) { $testConfig = [ 'language' => 'php', 'php' => $versions, ]; $createMatrix = function ($config) { return $this->createMatrix($config); }; $createMatrix = $createMatrix->bindTo($this->strategy, $this->strategy); $matrix = $createMatrix($testConfig); $this->assertAttributeContains((array) $versions, 'dimensions', $matrix); } /** * @return array */ public function createMatrixVersionDataProvider() { return [ // Test with float [5.5], // Test with string ['5.5'], // Test with list [['5.5', '5.6', '7']] ]; } } ================================================ FILE: tests/Joli/JoliCi/BuildStrategy/fixtures/jolici/project1/.jolici/test/Dockerfile ================================================ FROM ubuntu CMD echo 'lol' ================================================ FILE: tests/Joli/JoliCi/BuildStrategy/fixtures/jolici/project1/foo ================================================ bar ================================================ FILE: tests/Joli/JoliCi/BuildStrategy/fixtures/jolici/project2/.gitkeep ================================================ ================================================ FILE: tests/Joli/JoliCi/BuildStrategy/fixtures/travisci/project1/.travis.yml ================================================ language: php php: - 5.3 - 5.4 - 5.5 before_script: - git config --global user.name travis-ci - git config --global user.email travis@example.com script: - ./vendor/bin/phpunit ================================================ FILE: tests/Joli/JoliCi/JobTest.php ================================================ uniqid()); $descriptionMock = 'test'; $createdMock = new \DateTime(); $servicesMock = array('service' => uniqid()); $jobMock = new Job( $projectMock, $strategyMock, $uniqMock, $parametersMock, $descriptionMock, $createdMock, $servicesMock ); $this->assertAttributeSame($projectMock, 'project', $jobMock); $this->assertSame($strategyMock, $jobMock->getStrategy()); $this->assertSame($uniqMock, $jobMock->getUniq()); $this->assertSame($parametersMock, $jobMock->getParameters()); $this->assertSame($descriptionMock, $jobMock->getDescription()); $this->assertSame($createdMock, $jobMock->getCreated()); $this->assertSame($servicesMock, $jobMock->getServices()); return $jobMock; } /** * @param Job $jobMock * @depends testConstructInitialisesAllTheFields */ public function testAddAndGetServices($jobMock) { $serviceMock = new Service('test', 'test', 'test'); $currentServices = $jobMock->getServices(); $jobMock->addService($serviceMock); $this->assertEquals( array_merge($currentServices, array($serviceMock)), $jobMock->getServices() ); } public function testToStringReturnsTheNameOfTheJob() { $jobMock = new Job('project', 'strategy', uniqid()); $this->assertEquals($jobMock->getName(), $jobMock->__toString()); } } ================================================ FILE: tests/Joli/JoliCi/MatrixTest.php ================================================ setDimension('test', array()); $this->assertAttributeContains(array(null), 'dimensions', $matrixMock); } public function testComputeWillReturnEmptyIfNoDimensions() { $matrixMock = new Matrix(); $this->assertEquals(array(), $matrixMock->compute()); } public function testCompute() { $matrix = new Matrix(); $matrix->setDimension('a', array(1, 2, 3)); $matrix->setDimension('b', array(1, 2, 3)); $matrix->setDimension('c', array(1, 2, 3)); $possibilities = $matrix->compute(); $expected = array( array('a' => 1, 'b' => 1, 'c' => 1), array('a' => 1, 'b' => 1, 'c' => 2), array('a' => 1, 'b' => 1, 'c' => 3), array('a' => 1, 'b' => 2, 'c' => 1), array('a' => 1, 'b' => 2, 'c' => 2), array('a' => 1, 'b' => 2, 'c' => 3), array('a' => 1, 'b' => 3, 'c' => 1), array('a' => 1, 'b' => 3, 'c' => 2), array('a' => 1, 'b' => 3, 'c' => 3), array('a' => 2, 'b' => 1, 'c' => 1), array('a' => 2, 'b' => 1, 'c' => 2), array('a' => 2, 'b' => 1, 'c' => 3), array('a' => 2, 'b' => 2, 'c' => 1), array('a' => 2, 'b' => 2, 'c' => 2), array('a' => 2, 'b' => 2, 'c' => 3), array('a' => 2, 'b' => 3, 'c' => 1), array('a' => 2, 'b' => 3, 'c' => 2), array('a' => 2, 'b' => 3, 'c' => 3), array('a' => 3, 'b' => 1, 'c' => 1), array('a' => 3, 'b' => 1, 'c' => 2), array('a' => 3, 'b' => 1, 'c' => 3), array('a' => 3, 'b' => 2, 'c' => 1), array('a' => 3, 'b' => 2, 'c' => 2), array('a' => 3, 'b' => 2, 'c' => 3), array('a' => 3, 'b' => 3, 'c' => 1), array('a' => 3, 'b' => 3, 'c' => 2), array('a' => 3, 'b' => 3, 'c' => 3), ); $this->assertCount(count($expected), $possibilities); foreach ($expected as $value) { $this->assertContains($value, $possibilities); } } } ================================================ FILE: tests/Joli/JoliCi/ServiceTest.php ================================================ uniqid()); $serviceMock = new Service($nameMock, $repositoryMock, $tagMock, $configMock); $this->assertSame($nameMock, $serviceMock->getName()); $this->assertSame($repositoryMock, $serviceMock->getRepository()); $this->assertSame($tagMock, $serviceMock->getTag()); $this->assertSame($configMock, $serviceMock->getConfig()); return $serviceMock; } /** * @param Service $serviceMock * @depends testConstructInitialisesAllTheFields */ public function testSetAndGetContainer($serviceMock) { $dockerContainerMock = $this->getMockBuilder('Docker\Container') ->setMethods(null) ->getMock(); $serviceMock->setContainer($dockerContainerMock); $this->assertSame($dockerContainerMock, $serviceMock->getContainer()); } }