BehatShellExtension
Behat extension for executing shell commands within features. The shell commands can be run on remote
servers using ssh or locally without network. Additionally, local files can be deployed to directories
on remote servers., (*1)
Installation
Using composer:, (*2)
composer require postcon/behat-shell-extension dev-master
Usage
# run.feature
Feature: Running commands
In order to run useful integration tests
As a tester
I want to execute shell commands and check their output
Scenario: Run command on the default shell/server and define expected output
When I run "pwd"
Then It should pass
And I see
"""
/tmp
"""
Scenario: Run command on the default shell/server and define expected output in inline-style
When I run "pwd"
Then It should pass
And I see "/tmp"
Scenario: Run command on the shell/server "app"
When I run "app/console --env=prod do:mi:mi" on "app"
Then It should pass
# copy.feature
Feature: Copy file
In order to prepare integration tests
As a tester
I want to copy files to directories (on remote servers)
Scenario: Copy a file to /tmp directory on default server (or at the local filesystem)
Given I copy file "test.txt" to "/tmp"
And I run "cat /tmp/test.txt"
Then it should pass
And I see
"""
content of test.txt
"""
Scenario: Copy a file to /tmp directory on "app" server
Given I copy file "test.txt" to "/tmp" on "app"
And I run "cat /tmp/test.txt" on "app"
Then it should pass
And I see
"""
content of test.txt
"""
Configuration
To use the BehatShellExtension, it needs to be configured in the behat.yml
(or behat.yml.dist
).
Each server or shell, you want invoke commands on, must be specified., (*3)
Local shell
Following example shows the minimal configuration for a local shell., (*4)
# behat.yml
extensions:
ShellExtension:
default:
type: local
It is possible, to give two additional configuration parameters: the command executionbase_dir
and the
timeout
(in seconds; if the commands does not terminate within this timeout, it gets stopped and the behat feature
fails)., (*5)
# behat.yml
extensions:
ShellExtension:
default:
type: local
base_dir: /tmp
timeout: 10
Remote server / ssh
For accessing a remote server via ssh, a minimal configuration is like this:, (*6)
# behat.yml
extensions:
ShellExtension:
...
app:
type: remote
ssh_hostname: user@shell.example.com
The ssh_hostname
specifies the name of the ssh server and the username.
Using additional parameters, the ssh connection can be configured and the ssh and scp binaries can be specified:, (*7)
# behat.yml
extensions:
ShellExtension:
...
app:
type: remote
base_dir: /tmp
ssh_hostname: user@shell.example.com
ssh_options: -i ~/.ssh/id_rsa
ssh_command: /usr/bin/ssh
scp_command: /usr/bin/scp
timeout: 20
If we have this feature example, (*8)
Scenario:
Given I copy file "test.txt" to "/tmp" on "app"
And I run "cat /tmp/test.txt" on "app"
then the resulting commands would be this:, (*9)
/usr/bin/scp -i ~/.ssh/id_rsa 'test.txt' 'user@shell.example.com:/tmp'
/usr/bin/ssh -i ~/.ssh/id_rsa user@shell.example.com 'cd /tmp ; cat /tmp/test.txt'
Docker
To execute commands in a docker container, the following minimal configuration is appropriate:, (*10)
# behat.yml
extensions:
ShellExtension:
...
app:
type: docker
docker_containername: app
Here, we assume to have a docker container like this:, (*11)
docker run --name=app -d nginx
, (*12)
A more extensive configuration is this:, (*13)
# behat.yml
extensions:
ShellExtension:
...
app:
type: docker
base_dir: /tmp
docker_containername: app
docker_command: /usr/local/bin/docker
docker_options: -u user
timeout: 20
Here, the location of the docker executable is given and options, if needed., (*14)
If we have this feature example, (*15)
Scenario:
Given I copy file "test.txt" to "/tmp" on "app"
And I run "cat /tmp/test.txt" on "app"
then the resulting commands would be this:, (*16)
/usr/local/bin/docker cp 'test.txt' app:'/tmp'
/usr/local/bin/docker exec -u user app /bin/bash -c 'cd /tmp ; cat /tmp/test.txt'
Docker-Compose
By changing the parameter docker_command
, instead of a docker container, a docker-compose service can be used:, (*17)
# behat.yml
extensions:
ShellExtension:
app:
type: docker
base_dir: /tmp
docker_containername: app
docker_command: /usr/local/bin/docker-compose
docker-options: -T
timeout: 20
It is important to specify docker-options: -T
to Ā»Disable pseudo-tty allocationĀ«., (*18)
Here, we assume to have a docker-compose configuration like this:, (*19)
# docker-compose.yml
version: '2'
services:
app:
image: php:7.1-fpm
Right now, it is not possible to copy files into a running docker-compose service (i.e. a command
docker-compose cp
is missing)., (*20)
Internal implementation
A command string $command
is executed on a shell with type: local
gets invoked in following way:, (*21)
$process = new Process($command, $serverConfig['base_dir']);
$process->setTimeout($serverConfig['timeout']);
$process->run();
A remote executed command string $command
is executed this way:, (*22)
if ($serverConfig['base_dir']) {
$command = sprintf('cd %s ; %s', $serverConfig['base_dir'], $command);
}
$command = sprintf(
'%s %s %s %s',
$serverConfig['ssh_command'],
$serverConfig['ssh_options'],
$serverConfig['ssh_hostname'],
escapeshellarg($command)
);
// e.g. ssh -i ~/.ssh/id_rsa user@shell.example.com 'cd /var/www ; app/console --env=prod do:mi:mi'
$process = new Process($command);
$process->setTimeout($serverConfig['timeout']);
$process->run();
When using docker, a command string $command
is executed this way:, (*23)
if ($serverConfig['base_dir']) {
$command = sprintf('cd %s ; %s', $serverConfig['base_dir'], $command);
}
$command = sprintf(
'%s exec %s /bin/bash -c %s',
$serverConfig['docker_command'],
$serverConfig['docker_containername'],
escapeshellarg($command)
);
// e.g. docker exec container /bin/bash -c 'cd /var/www ; app/console --env=prod do:mi:mi'
$process = new Process($command);
$process->setTimeout($serverConfig['timeout']);
$process->run();
License
All contents of this package are licensed under the MIT license., (*24)