Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WORKAROUND kwarg custom_sentence in _get_match for working with outlines in hooks #554

Open
wants to merge 34 commits into
base: master
Choose a base branch
from
Open
Changes from 4 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9362151
First version of lxc isolator without world restore is finished.
Feb 8, 2013
06c8d8d
Enabling lxc isolator in lettuce
Feb 8, 2013
9125611
Finished
Feb 11, 2013
8a395b2
Merge pull request #1 from uty/master
Feb 11, 2013
e0e3b18
little docstring added
Feb 15, 2013
b90c3db
Added options for selective file loading and file excluding
Feb 28, 2013
41111c6
Merge pull request #2 from uty/master
Feb 28, 2013
9d8839e
Added feature that allows to define .py files to load in first line o…
Mar 11, 2013
b5f7d5c
Improved files to load finding algorithm
Mar 12, 2013
27047b5
removed debug print
Mar 12, 2013
f563e74
Merge pull request #3 from uty/master
Mar 14, 2013
2288d7c
wrapped sys.stdout
Mar 18, 2013
22adea4
fixed terrain import
Mar 18, 2013
74ddf7f
Merge pull request #4 from uty/master
uty Mar 18, 2013
c675ca4
terrain is now imported from directory with feature file by default
Mar 19, 2013
493af33
Merge pull request #5 from uty/master
uty Mar 19, 2013
05c3d23
Fixed files to load search when base path is path to .feature file
Mar 19, 2013
960d8b2
Merge pull request #6 from uty/master
uty Mar 19, 2013
1a45932
removed debug info
Mar 19, 2013
4591cfc
Merge branch 'master' of github.com:Scalr/lettuce
Mar 19, 2013
7840711
Fixed files to load finding now parses feature file given in base path
Mar 19, 2013
1c15566
changed failfast behaviour now adding last failed step to list of ste…
uty Apr 2, 2013
ff60af6
Merge pull request #7 from uty/master
uty Apr 2, 2013
631eb2e
changed failfast behaviour now stops scenario execution instead of fa…
uty Apr 2, 2013
6af4d2a
Merge pull request #8 from uty/master
uty Apr 2, 2013
cf68fa7
sync with upstream
uty Apr 2, 2013
72a4cc1
Changed tag matching behaviour - now excluding tags have higher
Apr 29, 2013
af38849
Merge branch 'master' of github.com:Scalr/lettuce
Apr 29, 2013
f1cc8fa
Improved the way that time_took is shown - now always showing minutes…
Apr 29, 2013
17b902f
Added fallback to original functionality when feature dir have no
May 6, 2013
912019b
Fixed time displaying
May 7, 2013
6bddb6b
merge from upstream
Oct 15, 2013
66931fe
Merge pull request #9 from uty/master
uty Oct 15, 2013
3abb2e1
WORKAROUND Add custom_sentence kwarg to _get_match method for handlin…
Theramas Sep 13, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lettuce/__init__.py
Original file line number Diff line number Diff line change
@@ -43,7 +43,8 @@
from lettuce.exceptions import StepLoadingError
from lettuce.plugins import (
xunit_output,
autopdb
autopdb,
lxc_isolator
)
from lettuce import fs
from lettuce import exceptions
252 changes: 252 additions & 0 deletions lettuce/plugins/lxc_isolator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import os
import pickle
import subprocess
import sys
import re
from time import sleep

from lettuce import core
from lettuce import world
from lettuce.terrain import after
from lettuce.terrain import before


LXC_RUNNER_TAG = 'lxc'


def system(cmd, *args):
p = subprocess.Popen(('%s ' % cmd) + ' '.join(args),
shell=True, stdout=subprocess.PIPE)
out, err = p.communicate()
return out, err, p.returncode


def lxc_command(cmd, *args):
return system('lxc-%s' % cmd, *args)


container_rc_local_template = '''#!/bin/sh
%(exports)s
cd %(work_dir)s
/usr/local/bin/lettuce -s %(scenario)s %(feature_path)s > %(results_path)s
halt
'''


class LXCRunner(object):
"""
Encapsulates scenario run in LXC container.
Performs container setup, scenario run and result displaying
"""

containers_path = '/var/lib/lxc/'
default_container_name = os.environ.get('LETTUCE_LXC_DEFAULT', 'default')
world_file_inner_path = '/root/world.dump'
run_results_inner_path = '/root/run_results'
run_results_str_regex = r'Scenario.*\n\n'
run_results_stats_regex = r'step(|s) \(.*\)'

def get_free_container_name(self, prefix):
"""
Iterate over containers_path and find free container name
with format '<prefix>.<num>' where num is in range of 00000-99999
"""

containers = filter(lambda x:
x.startswith(self.default_container_name + '.'),
os.listdir(self.containers_path))
containers.sort()
container_num = 0
if containers:
container_num = int(containers[-1].split('.')[1]) + 1
return '%s.%05d' % (self.default_container_name, container_num)

def __init__(self):
super(LXCRunner, self).__init__()
self.saved_runall = None
self.container_name = None
self.scenario = None

@property
def container_rootfs(self):
if not self.container_name:
return None
return os.path.join(self.containers_path,
self.container_name,
'rootfs')

def lxc_abs_path(self, path):
if path.startswith('/'):
path = path[1:]
return os.path.join(self.container_rootfs, path)

@property
def scenario_index(self):
scenario_list = self.scenario.feature.scenarios
return scenario_list.index(self.scenario) + 1

def run_scenario(self, scenario):
self.scenario = scenario
self.setup_container()
world_path = self.lxc_abs_path(self.world_file_inner_path)
self.save_world(world_path)
self.run_container()
self.wait_container()
results = self.get_run_results()
self.shutdown_container()
self.display_results(results[0])
return results[1]

def create_container(self):
self.container_name = self.get_free_container_name(self.default_container_name)
lxc_command('clone', '-o %s -n %s' % (self.default_container_name,
self.container_name))

def setup_container(self):
self.create_container()

system('cp', '-rf',
'/vagrant',
self.lxc_abs_path('/vagrant'))
container_working_dir = os.path.abspath('.')
feature_path = sys.argv[-1]

# setup start scripts
# we assume that created container already have lettuce installed
script_path = self.lxc_abs_path('/etc/rc.local')
with open(script_path, 'w') as fp:
def _export_env_var(acc, keyvalue):
return "%sexport %s='%s'\n" % ((acc,) + keyvalue)

export_str = reduce(_export_env_var, os.environ.items(), '')

fp.write(container_rc_local_template % {
'exports': export_str,
'work_dir': container_working_dir,
'scenario': self.scenario_index,
'feature_path': feature_path,
'results_path': self.run_results_inner_path})
os.chmod(script_path, 0755)

def save_world(self, filepath):
with open(filepath, 'w') as f:
for var in dir(world):
if not var.startswith('_') and var not in ('absorb', 'spew'):
pickle.dump((var, world.__getattribute__(var)), f)

def load_world(self, path):
with open(path, 'r') as f:
while True:
try:
attr = pickle.load(f)
world.__setattr__(attr[0], attr[1])
except EOFError:
break

def run_container(self):
return_code = lxc_command('start',
'-d',
'-n ' + self.container_name)[2]
if return_code != 0:
raise BaseException('Container failed to start')

def wait_container(self):
"""
Waits for run_results_inner_path with /proc/x poll.
"""

while True:
ps_out = system('ps', 'auxf')[0]

match = re.search(r'-n %s.*' % self.container_name,
ps_out,
re.DOTALL)
if not match:
return

match = re.search(r'.*lettuce.*', match.group())
if match:
lxc_lettuce_pid = match.group().split()[1]
while os.path.exists('/proc/%s' % lxc_lettuce_pid):
sleep(1)
return
sleep(1)

def get_run_results(self):
"""
Reads file on run_results_inner_path.
Returns pair with string representation of step run result
and tuple of number failed, skipped and passed steps
"""
path = self.lxc_abs_path(self.run_results_inner_path)
with open(path, 'r') as fp:
lettuce_out = fp.read()
match = re.search(self.run_results_str_regex,
lettuce_out,
re.DOTALL)
results = match.group()
second_line_start = results.index('\n') + 1

run_result_str = results[second_line_start:].strip()

# statistics
match = re.search(self.run_results_stats_regex, lettuce_out)
stats = match.group()

def _get_steps_num(type_):
match = re.search(r'\d+ %s' % type_, stats)
if not match:
return 0
match_str = match.group()
return int(match_str.split()[0])

failed_num = _get_steps_num('failed')
skipped_num = _get_steps_num('skipped')
passed_num = _get_steps_num('passed')

stats_tuple = (failed_num, skipped_num, passed_num)
return (run_result_str, stats_tuple)

def shutdown_container(self):
# lxc_command('stop', '-n ' + self.container_name)
lxc_command('destroy', '-n ' + self.container_name)

def display_results(self, results):
# just print results... or use some output plugin
print results


lxc_runner = LXCRunner()


@before.each_scenario
def handle_lxc_tag_setup(scenario):
if LXC_RUNNER_TAG in scenario.tags:
# if world dump file is presented, lettuce is runned in LXC
# so we need to restore world
if os.path.exists(lxc_runner.world_file_inner_path):
lxc_runner.load_world(lxc_runner.world_file_inner_path)
return

for step in scenario.steps:
step.passed = True
step.run = lambda *args, **kwargs: True
step.ran = True

lxc_runner.saved_runall = core.Step.run_all

def run_all_mock(*args, **kwargs):
failed, skipped, passed = lxc_runner.run_scenario(scenario)
return (scenario.steps, # all
scenario.steps[:passed], # passed
scenario.steps[passed:passed + failed], # failed
[], # undefined
[]) # reasons to fail

core.Step.run_all = staticmethod(run_all_mock)


@after.each_scenario
def handle_lxc_tag_teardown(scenario):
if LXC_RUNNER_TAG in scenario.tags and not os.path.exists(lxc_runner.world_file_inner_path):
core.Step.run_all = staticmethod(lxc_runner.saved_runall)