blob: 240ef5842298d8b70b869e572d2c2392cf33ec4e [file] [log] [blame]
#!/usr/bin/python -u
#
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""The unittest for the main factory flow that runs the factory test."""
from __future__ import print_function
import factory_common # pylint: disable=W0611
import logging
import math
import mox
import pickle
import re
import tempfile
import threading
import time
import unittest
from mox import IgnoreArg
from ws4py.client import WebSocketBaseClient
from cros.factory.goofy_monolithic import goofy
from cros.factory.test import factory
from cros.factory.test import state
from cros.factory.goofy_monolithic.connection_manager import ConnectionManager
from cros.factory.goofy_monolithic import invocation
from cros.factory.goofy_monolithic.goofy import Goofy
from cros.factory.goofy_monolithic.test_environment import Environment
from cros.factory.test import shopfloor
from cros.factory.test.event import Event
from cros.factory.test.factory import TestState
from cros.factory.utils import net_utils
from cros.factory.utils import test_utils
from cros.factory.utils.process_utils import Spawn
def init_goofy(env=None, test_list=None, options='', restart=True, ui='none'):
"""Initializes and returns a Goofy."""
new_goofy = Goofy()
args = ['--ui', ui]
if restart:
args.append('--restart')
if test_list:
out = tempfile.NamedTemporaryFile(prefix='test_list', delete=False)
# Remove whitespace at the beginning of each line of options.
options = re.sub('(?m)^\s+', '', options)
out.write('TEST_LIST = [' + test_list + ']\n' + options)
out.close()
args.extend(['--test_list', out.name])
logging.info('Running goofy with args %r', args)
new_goofy.init(args, env or Environment())
return new_goofy
def mock_autotest(env, name, passed, error_msg):
"""Adds a side effect that a mock autotest will be executed.
Args:
env: The mock Environment object.
name: The name of the autotest to be mocked.
passed: Whether the test should pass.
error_msg: The error message.
"""
def side_effect(unused_name, unused_args, unused_env_additions, result_file):
with open(result_file, 'w') as out:
pickle.dump((passed, error_msg), out)
return Spawn(['true'])
env.spawn_autotest(
name, IgnoreArg(), IgnoreArg(), IgnoreArg()).WithSideEffects(side_effect)
def mock_pytest(spawn, name, test_state, error_msg, func=None):
"""Adds a side effect that a mock pytest will be executed.
Args:
spawn: The mock Spawn object.
name: The name of the pytest to be mocked.
test_state: The resulting test state.
error_msg: The error message.
func: Optional callable to run inside the side effect function.
"""
def side_effect(args, **kwargs):
info = pickle.load(open(args[-1]))
assert info.pytest_name == name
if func:
func()
with open(info.results_path, 'w') as out:
pickle.dump((test_state, error_msg), out)
return Spawn(['true'], **kwargs)
spawn(IgnoreArg(), env=IgnoreArg(), stdin=IgnoreArg(), stdout=IgnoreArg(),
stderr=IgnoreArg()).WithSideEffects(side_effect)
class GoofyTest(unittest.TestCase):
"""Base class for Goofy test cases."""
options = ''
ui = 'none'
expected_create_connection_manager_args = (
[], factory.Options.scan_wifi_period_secs)
test_list = None # Overridden by subclasses
def setUp(self):
# Log the name of the test we're about to run, to make it easier
# to grok the logs.
logging.info('*** Running test %s', type(self).__name__)
state.DEFAULT_FACTORY_STATE_PORT = test_utils.FindUnusedTCPPort()
logging.info('Using port %d for factory state',
state.DEFAULT_FACTORY_STATE_PORT)
self.mocker = mox.Mox()
self.env = self.mocker.CreateMock(Environment)
self.state = state.get_instance()
self.connection_manager = self.mocker.CreateMock(ConnectionManager)
self.env.create_connection_manager(
*self.expected_create_connection_manager_args).AndReturn(
self.connection_manager)
self.before_init_goofy()
self.mocker.ReplayAll()
self.goofy = init_goofy(self.env, self.test_list, self.options,
ui=self.ui)
self.mocker.VerifyAll()
self.mocker.ResetAll()
def tearDown(self):
self.goofy.destroy()
# Make sure we're not leaving any extra threads hanging around
# after a second.
for _ in range(10):
extra_threads = [t for t in threading.enumerate()
if t != threading.current_thread()]
if not extra_threads:
break
logging.info('Waiting for %d threads to die', len(extra_threads))
# Wait another 100 ms
time.sleep(.1)
self.assertEqual([], extra_threads)
def _wait(self):
"""Waits for any pending invocations in Goofy to complete.
Waits for any pending invocations in Goofy to complete,
and verifies and resets all mocks.
"""
self.goofy.wait()
self.mocker.VerifyAll()
self.mocker.ResetAll()
def before_init_goofy(self):
"""Hook invoked before init_goofy."""
def check_one_test(self, test_id, name, passed, error_msg, trigger=None,
does_not_start=False, setup_mocks=True, expected_count=1):
"""Runs a single autotest, waiting for it to complete.
Args:
test_id: The ID of the test expected to run.
name: The autotest name of the test expected to run.
passed: Whether the test should pass.
error_msg: The error message, if any.
trigger: An optional callable that will be executed after mocks are
set up to trigger the autotest. If None, then the test is
expected to start itself.
does_not_start: If True, checks that the test is not expected to start
(e.g., due to an unsatisfied require_run).
setup_mocks: If True, sets up mocks for the test runs.
expected_count: The expected run count.
"""
if setup_mocks and not does_not_start:
mock_autotest(self.env, name, passed, error_msg)
self.mocker.ReplayAll()
if trigger:
trigger()
self.assertTrue(self.goofy.run_once())
self.assertEqual([] if does_not_start else [test_id],
[test.path for test in self.goofy.invocations])
self._wait()
test_state = self.state.get_test_state(test_id)
self.assertEqual(TestState.PASSED if passed else TestState.FAILED,
test_state.status)
self.assertEqual(0 if does_not_start else expected_count, test_state.count)
self.assertEqual(error_msg, test_state.error_msg)
# A simple test list with three tests.
ABC_TEST_LIST = """
OperatorTest(id='a', autotest_name='a_A'),
OperatorTest(id='b', autotest_name='b_B'),
OperatorTest(id='c', autotest_name='c_C'),
"""
class BasicTest(GoofyTest):
"""A simple test case that checks that tests are run in the correct order."""
test_list = ABC_TEST_LIST
def runTest(self):
self.check_one_test('a', 'a_A', True, '')
self.check_one_test('b', 'b_B', False, 'Uh-oh')
self.check_one_test('c', 'c_C', False, 'Uh-oh')
self.assertEqual(
'id: null\n'
'path: null\n'
'subtests:\n'
'- {count: 1, error_msg: null, id: a, path: a, status: PASSED}\n'
'- {count: 1, error_msg: Uh-oh, id: b, path: b, status: FAILED}\n'
'- {count: 1, error_msg: Uh-oh, id: c, path: c, status: FAILED}\n',
self.goofy.test_list.as_yaml(
factory.get_state_instance().get_test_states()))
class WebSocketTest(GoofyTest):
"""A test case that checks the behavior of web sockets."""
test_list = ABC_TEST_LIST
ui = 'chrome'
def __init__(self, *args, **kwargs):
super(WebSocketTest, self).__init__(*args, **kwargs)
self.events = None
self.ws_done = None
def before_init_goofy(self):
# Keep a record of events we received
self.events = []
# Trigger this event once the web socket closes
self.ws_done = threading.Event()
class MyClient(WebSocketBaseClient):
"""The web socket client class."""
# pylint: disable=E0213
def handshake_ok(socket_self):
pass
def received_message(socket_self, message):
event = Event.from_json(str(message))
logging.info('Test client received %s', event)
self.events.append(event)
if event.type == Event.Type.HELLO:
socket_self.send(Event(Event.Type.KEEPALIVE,
uuid=event.uuid).to_json())
ws = MyClient('ws://%s:%d/event' %
(net_utils.LOCALHOST, state.DEFAULT_FACTORY_STATE_PORT),
protocols=None, extensions=None)
def open_web_socket():
ws.connect()
# Simulate setting the test widget size/position, since goofy
# waits for it.
factory.set_shared_data('test_widget_size', [100, 200],
'test_widget_position', [300, 400])
ws.run()
self.ws_done.set()
# pylint: disable=W0108
self.env.launch_chrome().WithSideEffects(
lambda: threading.Thread(target=open_web_socket).start()
).AndReturn(None)
def runTest(self):
self.check_one_test('a', 'a_A', True, '')
self.check_one_test('b', 'b_B', False, 'Uh-oh')
self.check_one_test('c', 'c_C', False, 'Uh-oh')
# Kill Goofy and wait for the web socket to close gracefully
self.goofy.destroy()
self.ws_done.wait()
events_by_type = {}
for event in self.events:
events_by_type.setdefault(event.type, []).append(event)
# There should be one hello event
self.assertEqual(1, len(events_by_type[Event.Type.HELLO]))
# Each test should have a transition to active, a transition to
# active + visible, and then to its final state
for path, final_status in (('a', TestState.PASSED),
('b', TestState.FAILED),
('c', TestState.FAILED)):
statuses = [
event.state['status']
for event in events_by_type[Event.Type.STATE_CHANGE]
if event.path == path]
self.assertEqual(
['ACTIVE', 'ACTIVE', final_status],
statuses)
class ShutdownTest(GoofyTest):
"""A test case that checks the behavior of shutdown."""
test_list = """
RebootStep(id='shutdown', iterations=3),
OperatorTest(id='a', autotest_name='a_A')
"""
def runTest(self):
# Stub out invocation.Spawn to mock pytest invocation.
invocation.Spawn = self.mocker.CreateMock(Spawn)
# Expect a reboot request.
mock_pytest(invocation.Spawn, 'shutdown', TestState.ACTIVE, '',
func=lambda: self.goofy.shutdown('reboot'))
self.env.shutdown('reboot').AndReturn(True)
self.mocker.ReplayAll()
self.assertTrue(self.goofy.run_once())
self._wait()
# That should have enqueued a task that will cause Goofy
# to shut down.
self.assertFalse(self.goofy.run_once())
# There should be a list of tests to run on wake-up.
self.assertEqual(
['a'], self.state.get_shared_data('tests_after_shutdown'))
self._wait()
# Kill and restart Goofy to simulate the first two shutdown iterations.
# Goofy should call for another shutdown.
for _ in range(2):
self.mocker.ResetAll()
self.env.create_connection_manager(
[], factory.Options.scan_wifi_period_secs).AndReturn(
self.connection_manager)
# Goofy should invoke shutdown test to do post-shutdown verification.
mock_pytest(invocation.Spawn, 'shutdown', TestState.PASSED, '')
# Goofy should invoke shutdown again to start next iteration.
mock_pytest(invocation.Spawn, 'shutdown', TestState.ACTIVE, '',
func=lambda: self.goofy.shutdown('reboot'))
self.env.shutdown('reboot').AndReturn(True)
self.mocker.ReplayAll()
self.goofy.destroy()
self.goofy = init_goofy(self.env, self.test_list, restart=False)
self.goofy.run_once()
self._wait()
# The third shutdown iteration.
self.mocker.ResetAll()
self.env.create_connection_manager(
[], factory.Options.scan_wifi_period_secs).AndReturn(
self.connection_manager)
# Goofy should invoke shutdown test to do post-shutdown verification.
mock_pytest(invocation.Spawn, 'shutdown', TestState.PASSED, '')
self.mocker.ReplayAll()
self.goofy.destroy()
self.goofy = init_goofy(self.env, self.test_list, restart=False)
self.goofy.run_once()
self._wait()
# No more shutdowns - now 'a' should run.
self.check_one_test('a', 'a_A', True, '')
class RebootFailureTest(GoofyTest):
"""A test case that checks the behavior of reboot failure."""
test_list = """
RebootStep(id='shutdown'),
"""
def runTest(self):
# Stub out invocation.Spawn to mock pytest invocation.
invocation.Spawn = self.mocker.CreateMock(Spawn)
# Expect a reboot request
mock_pytest(invocation.Spawn, 'shutdown', TestState.ACTIVE, '',
func=lambda: self.goofy.shutdown('reboot'))
self.env.shutdown('reboot').AndReturn(True)
self.mocker.ReplayAll()
self.assertTrue(self.goofy.run_once())
self._wait()
# That should have enqueued a task that will cause Goofy
# to shut down.
self.mocker.ReplayAll()
self.assertFalse(self.goofy.run_once())
self._wait()
# Something pretty close to the current time should be written
# as the shutdown time.
shutdown_time = self.state.get_shared_data('shutdown_time')
self.assertTrue(math.fabs(time.time() - shutdown_time) < 2)
# Kill and restart Goofy to simulate a reboot.
# Goofy should fail the test since it has been too long.
self.goofy.destroy()
self.mocker.ResetAll()
self.env.create_connection_manager(
[], factory.Options.scan_wifi_period_secs).AndReturn(
self.connection_manager)
# Mock a failed shutdown post-shutdown verification.
mock_pytest(invocation.Spawn, 'shutdown', TestState.FAILED,
'Reboot failed.')
self.mocker.ReplayAll()
self.goofy = init_goofy(self.env, self.test_list, restart=False)
self.goofy.run_once()
self._wait()
test_state = factory.get_state_instance().get_test_state('shutdown')
self.assertEquals(TestState.FAILED, test_state.status)
logging.info('%s', test_state.error_msg)
self.assertTrue(test_state.error_msg.startswith(
'Reboot failed.'))
class NoAutoRunTest(GoofyTest):
"""A test case that checks the behavior when auto_run_on_start is False."""
test_list = ABC_TEST_LIST
options = 'options.auto_run_on_start = False'
def _runTestB(self):
# There shouldn't be anything to do at startup, since auto_run_on_start
# is unset.
self.mocker.ReplayAll()
self.goofy.run_once()
self.assertEqual({}, self.goofy.invocations)
self._wait()
# Tell Goofy to run 'b'.
self.check_one_test(
'b', 'b_B', True, '',
trigger=lambda: self.goofy.handle_switch_test(
Event(Event.Type.SWITCH_TEST, path='b')))
def runTest(self):
self._runTestB()
# No more tests to run now.
self.mocker.ReplayAll()
self.goofy.run_once()
self.assertEqual({}, self.goofy.invocations)
class AutoRunKeypressTest(NoAutoRunTest):
"""A test case that checks the behavior of auto_run_on_keypress."""
test_list = ABC_TEST_LIST
options = """
options.auto_run_on_start = False
options.auto_run_on_keypress = True
"""
def runTest(self):
self._runTestB()
# Unlike in NoAutoRunTest, C should now be run.
self.check_one_test('c', 'c_C', True, '')
class PyTestTest(GoofyTest):
"""Tests the Python test driver.
Note that no mocks are used here, since it's easy enough to just have the
Python driver run a 'real' test (execpython).
"""
test_list = """
OperatorTest(id='a', pytest_name='execpython',
dargs={'script': 'assert "Tomato" == "Tomato"'}),
OperatorTest(id='b', pytest_name='execpython',
dargs={'script': ("assert 'Pa-TAY-to' == 'Pa-TAH-to', "
"'Let\\\\\'s call the whole thing off'")})
"""
def runTest(self):
# Some other test cases might stub out invocation.Spawn, ex: ShutdownTest.
# To run the 'real' test (execpython), we need to re-assign it to the
# 'real' process_utils.Spawn.
invocation.Spawn = Spawn
self.goofy.run_once()
self.assertEquals(['a'],
[test.id for test in self.goofy.invocations])
self.goofy.wait()
self.assertEquals(
TestState.PASSED,
factory.get_state_instance().get_test_state('a').status)
self.goofy.run_once()
self.assertEquals(['b'],
[test.id for test in self.goofy.invocations])
self.goofy.wait()
failed_state = factory.get_state_instance().get_test_state('b')
self.assertEquals(TestState.FAILED, failed_state.status)
self.assertTrue(
"""Let's call the whole thing off""" in failed_state.error_msg,
failed_state.error_msg)
class PyLambdaTest(GoofyTest):
"""A test case that checks the behavior of execpython."""
test_list = """
OperatorTest(id='a', pytest_name='execpython',
dargs={'script': lambda env: 'raise ValueError("It"+"Failed")'})
"""
def runTest(self):
# Some other test cases might stub out invocation.Spawn, ex: ShutdownTest.
# To run the 'real' test (execpython), we need to re-assign it to the
# 'real' process_utils.Spawn.
invocation.Spawn = Spawn
self.goofy.run_once()
self.goofy.wait()
failed_state = factory.get_state_instance().get_test_state('a')
self.assertEquals(TestState.FAILED, failed_state.status)
self.assertTrue(
"""ItFailed""" in failed_state.error_msg,
failed_state.error_msg)
class MultipleIterationsTest(GoofyTest):
"""Tests running a test multiple times."""
test_list = """
OperatorTest(id='a', autotest_name='a_A'),
OperatorTest(id='b', autotest_name='b_B', iterations=3),
OperatorTest(id='c', autotest_name='c_C', iterations=3),
OperatorTest(id='d', autotest_name='d_D'),
"""
def runTest(self):
self.check_one_test('a', 'a_A', True, '')
mock_autotest(self.env, 'b_B', True, '')
mock_autotest(self.env, 'b_B', True, '')
mock_autotest(self.env, 'b_B', True, '')
self.check_one_test('b', 'b_B', True, '', setup_mocks=False,
expected_count=3)
mock_autotest(self.env, 'c_C', True, '')
mock_autotest(self.env, 'c_C', False, 'I bent my wookie')
# iterations=3, but it should stop after the first failed iteration.
self.check_one_test('c', 'c_C', False, 'I bent my wookie',
setup_mocks=False, expected_count=2)
self.check_one_test('d', 'd_D', True, '')
class ConnectionManagerTest(GoofyTest):
"""Tests connection manager."""
options = """
options.wlans = [WLAN('foo', 'psk', 'bar')]
"""
test_list = """
OperatorTest(id='a', autotest_name='a_A'),
TestGroup(id='b', exclusive='NETWORKING', subtests=[
OperatorTest(id='b1', autotest_name='b_B1'),
OperatorTest(id='b2', autotest_name='b_B2'),
]),
OperatorTest(id='c', autotest_name='c_C'),
"""
expected_create_connection_manager_args = (mox.Func(
lambda arg: (len(arg) == 1 and
arg[0].__dict__ == dict(ssid='foo',
security='psk',
passphrase='bar'))),
factory.Options.scan_wifi_period_secs)
def runTest(self):
self.check_one_test('a', 'a_A', True, '')
self.connection_manager.DisableNetworking()
self.check_one_test('b.b1', 'b_B1', False, 'Uh-oh')
self.check_one_test('b.b2', 'b_B2', False, 'Uh-oh')
self.connection_manager.EnableNetworking()
self.check_one_test('c', 'c_C', True, '')
class RequireRunTest(GoofyTest):
"""Tests FactoryTest require_run argument."""
options = """
options.auto_run_on_start = False
"""
test_list = """
OperatorTest(id='a', autotest_name='a_A'),
OperatorTest(id='b', autotest_name='b_B', require_run='a'),
"""
def runTest(self):
self.goofy.restart_tests(
root=self.goofy.test_list.lookup_path('b'))
self.check_one_test('b', 'b_B', False,
'Required tests [a] have not been run yet',
does_not_start=True)
self.goofy.restart_tests()
self.check_one_test('a', 'a_A', True, '')
self.check_one_test('b', 'b_B', True, '')
class RequireRunPassedTest(GoofyTest):
"""Tests FactoryTest require_run argument with Passed syntax."""
options = """
options.auto_run_on_start = True
"""
test_list = """
OperatorTest(id='a', autotest_name='a_A'),
OperatorTest(id='b', autotest_name='b_B', require_run=Passed('a')),
"""
def runTest(self):
self.check_one_test('a', 'a_A', False, '')
self.check_one_test('b', 'b_B', False,
'Required tests [a] have not been run yet',
does_not_start=True)
self.goofy.restart_tests()
self.check_one_test('a', 'a_A', True, '', expected_count=2)
self.check_one_test('b', 'b_B', True, '')
class RunIfTest(GoofyTest):
"""Tests FactoryTest run_if argument."""
options = """
options.auto_run_on_start = True
"""
test_list = """
OperatorTest(id='a', autotest_name='a_A', run_if='foo.bar'),
OperatorTest(id='b', autotest_name='b_B', run_if='!foo.bar'),
OperatorTest(id='c', autotest_name='c_C'),
"""
def runTest(self):
state_instance = factory.get_state_instance()
# Set foo.bar in the state server.
shopfloor.save_aux_data('foo', 'MLB00001', {'bar': True})
self.goofy.update_skipped_tests()
a_state = state_instance.get_test_state('a')
self.assertEquals(False, a_state.skip)
b_state = state_instance.get_test_state('b')
self.assertEquals(True, b_state.skip)
self.check_one_test('a', 'a_A', True, '')
self.check_one_test('c', 'c_C', True, '')
a_state = state_instance.get_test_state('a')
self.assertEquals(TestState.PASSED, a_state.status)
self.assertEquals('', a_state.error_msg)
b_state = state_instance.get_test_state('b')
self.assertEquals(TestState.PASSED, b_state.status)
self.assertEquals(TestState.SKIPPED_MSG, b_state.error_msg)
# Set foo.bar=False. The state of b_B should switch to untested.
shopfloor.save_aux_data('foo', 'MLB00001', {'bar': False})
self.goofy.update_skipped_tests()
a_state = state_instance.get_test_state('a')
self.assertEquals(TestState.PASSED, a_state.status)
self.assertEquals('', a_state.error_msg)
b_state = state_instance.get_test_state('b')
self.assertEquals(TestState.UNTESTED, b_state.status)
self.assertEquals('', b_state.error_msg)
class GroupRunIfTest(GoofyTest):
"""Tests TestGroup run_if argument."""
options = """
options.auto_run_on_start = True
"""
test_list = """
TestGroup(id='G1', run_if='foo.g1', subtests=[
OperatorTest(id='T1', autotest_name='a_A', run_if='foo.t1'),
OperatorTest(id='T2', autotest_name='a_A', run_if='foo.t2'),
OperatorTest(id='T3', autotest_name='a_A', run_if='foo.t3'),
OperatorTest(id='T4', autotest_name='a_A'),
])
"""
def runTest(self):
state_instance = factory.get_state_instance()
def _check_state(id_state_dict):
for test_id, skip_status_msg in id_state_dict.iteritems():
test_state = state_instance.get_test_state(test_id)
skip, status, msg = skip_status_msg
self.assertEquals(skip, test_state.skip)
self.assertEquals(status, test_state.status)
self.assertEquals(msg, test_state.error_msg)
# Keeps group G1 but skips G1.T1.
# G1.T1 should be the only test to skip.
shopfloor.save_aux_data('foo', 'MLB00001',
{'g1': True,
't1': False,
't2': True,
't3': True})
self.goofy.update_skipped_tests()
_check_state(
{'G1': (False, TestState.UNTESTED, None),
'G1.T1': (True, TestState.UNTESTED,None),
'G1.T2': (False, TestState.UNTESTED,None),
'G1.T3': (False, TestState.UNTESTED,None),
'G1.T4': (False, TestState.UNTESTED,None)})
# Disables group G1. All tests are skipped.
shopfloor.save_aux_data('foo', 'MLB00001',
{'g1' : False,
't1' : False,
't2' : True,
't3' : True})
self.goofy.update_skipped_tests()
_check_state(
{'G1': (True, TestState.UNTESTED, None),
'G1.T1': (True, TestState.UNTESTED, None),
'G1.T2': (True, TestState.UNTESTED, None),
'G1.T3': (True, TestState.UNTESTED, None ),
'G1.T4': (True, TestState.UNTESTED, None)})
# Runs group G1. All tests are skipped and passed.
for _ in range(4):
self.assertTrue(self.goofy.run_once())
self.goofy.wait()
_check_state(
{'G1': (True, TestState.PASSED, None),
'G1.T1': (True, TestState.PASSED, TestState.SKIPPED_MSG),
'G1.T2': (True, TestState.PASSED, TestState.SKIPPED_MSG),
'G1.T3': (True, TestState.PASSED, TestState.SKIPPED_MSG),
'G1.T4': (True, TestState.PASSED, TestState.SKIPPED_MSG)})
# Re-enable group G1, but skips G1.T1.
# G1, G1.T2, G1.T3, G1.T4 should not be skipped now. Also, they
# should be untested.
shopfloor.save_aux_data('foo', 'MLB00001',
{'g1' : True,
't1' : False,
't2' : True,
't3' : True})
self.goofy.update_skipped_tests()
_check_state(
{'G1': (False, TestState.UNTESTED, None),
'G1.T1': (True, TestState.PASSED, TestState.SKIPPED_MSG),
'G1.T2': (False, TestState.UNTESTED, ''),
'G1.T3': (False, TestState.UNTESTED, ''),
'G1.T4': (False, TestState.UNTESTED, '')})
class GroupRunIfSkipTest(GoofyTest):
"""Tests TestGroup run_if argument and skip method."""
options = """
options.auto_run_on_start = False
"""
test_list = """
TestGroup(id='G1', run_if='foo.g1', subtests=[
OperatorTest(id='T1', autotest_name='a_A', run_if='foo.t1'),
OperatorTest(id='T2', autotest_name='a_A', run_if='foo.t2'),
OperatorTest(id='T3', autotest_name='a_A', run_if='foo.t3'),
OperatorTest(id='T4', autotest_name='a_A'),
])
"""
def runTest(self):
state_instance = factory.get_state_instance()
def _check_state(id_state_dict):
for test_id, skip_status_msg in id_state_dict.iteritems():
test_state = state_instance.get_test_state(test_id)
skip, status, msg = skip_status_msg
self.assertEquals(skip, test_state.skip)
self.assertEquals(status, test_state.status)
self.assertEquals(msg, test_state.error_msg)
# Enables group G1, but skips G1.T1.
# G1, G1.T2, G1.T3, G1.T4 should not be skipped. Also, they
# should be untested.
shopfloor.save_aux_data('foo', 'MLB00001',
{'g1' : True,
't1' : False,
't2' : True,
't3' : True})
self.goofy.update_skipped_tests()
_check_state(
{'G1': (False, TestState.UNTESTED, None),
'G1.T1': (True, TestState.UNTESTED, None),
'G1.T2': (False, TestState.UNTESTED, None),
'G1.T3': (False, TestState.UNTESTED, None),
'G1.T4': (False, TestState.UNTESTED, None)})
# Runs and Fails G1.T3 test.
self.check_one_test('G1.T3', 'a_A', False, 'Uh-oh',
trigger=lambda: self.goofy.handle_switch_test(
Event(Event.Type.SWITCH_TEST, path='G1.T3')))
# Runs and Passes G1.T4 test.
self.check_one_test('G1.T4', 'a_A', True, '',
trigger=lambda: self.goofy.handle_switch_test(
Event(Event.Type.SWITCH_TEST, path='G1.T4')))
# Now G1.T3 status is FAILED, and G1.T4 status is PASSED.
# Now G1 status is FAILED because G1.T3 status is FAILED.
_check_state(
{'G1': (False, TestState.FAILED, None),
'G1.T1': (True, TestState.UNTESTED, None),
'G1.T2': (False, TestState.UNTESTED, None),
'G1.T3': (False, TestState.FAILED, 'Uh-oh'),
'G1.T4': (False, TestState.PASSED, '')})
# Skips G1 on purpose. Then all tests should be skipped.
# G1.T4 has already passed, so its error_msg should not be modified.
self.goofy.test_list.lookup_path('G1').skip()
_check_state(
{'G1': (True, TestState.PASSED, TestState.SKIPPED_MSG),
'G1.T1': (True, TestState.PASSED, TestState.SKIPPED_MSG),
'G1.T2': (True, TestState.PASSED, TestState.SKIPPED_MSG),
'G1.T3': (True, TestState.PASSED, TestState.SKIPPED_MSG),
'G1.T4': (False, TestState.PASSED, '')})
# update_skipped_tests should not re-enable G1 test group.
# It only modifies the skip status of G1.T4 from False to True.
self.goofy.update_skipped_tests()
_check_state(
{'G1': (True, TestState.PASSED, TestState.SKIPPED_MSG),
'G1.T1': (True, TestState.PASSED, TestState.SKIPPED_MSG),
'G1.T2': (True, TestState.PASSED, TestState.SKIPPED_MSG),
'G1.T3': (True, TestState.PASSED, TestState.SKIPPED_MSG),
'G1.T4': (True, TestState.PASSED, '')})
class StopOnFailureTest(GoofyTest):
"""A unittest that checks if the goofy will stop after a test fails."""
test_list = ABC_TEST_LIST
options = """
options.auto_run_on_start = True
options.stop_on_failure = True
"""
def runTest(self):
mock_autotest(self.env, 'a_A', True, '')
mock_autotest(self.env, 'b_B', False, 'Oops!')
self.mocker.ReplayAll()
# Make sure events are all processed.
for _ in range(3):
self.assertTrue(self.goofy.run_once())
self.goofy.wait()
state_instance = factory.get_state_instance()
self.assertEquals(
[TestState.PASSED, TestState.FAILED, TestState.UNTESTED],
[state_instance.get_test_state(x).status for x in ['a', 'b', 'c']])
self._wait()
class ForceBackgroundTest(GoofyTest):
"""Tests force_background parameter in test list.
We have three kinds of the next eligible test:
1. normal
2. backgroundable
3. force_background
And we have four situations of the ongoing invocations:
a. only a running normal test
b. all running tests are backgroundable
c. all running tests are force_background
d. all running tests are any combination of backgroundable and
force_background
When a test would like to be run, it must follow the rules:
[1] cannot run with [abd]
[2] cannot run with [a]
All the other combinations are allowed
"""
test_list = """
FactoryTest(id='aA', pytest_name='a_A'),
FactoryTest(id='bB', pytest_name='b_B'),
FactoryTest(id='cC', pytest_name='c_C', backgroundable=True),
FactoryTest(id='dD', pytest_name='d_D', force_background=True),
FactoryTest(id='eE', pytest_name='e_E'),
FactoryTest(id='fF', pytest_name='f_F', force_background=True),
FactoryTest(id='gG', pytest_name='g_G', backgroundable=True),
"""
def runTest(self):
# Stub out invocation.Spawn to mock pytest invocation.
invocation.Spawn = self.mocker.CreateMock(Spawn)
mock_pytest(invocation.Spawn, 'a_A', TestState.PASSED, '')
mock_pytest(invocation.Spawn, 'b_B', TestState.PASSED, '')
mock_pytest(invocation.Spawn, 'c_C', TestState.PASSED, '')
mock_pytest(invocation.Spawn, 'd_D', TestState.PASSED, '')
mock_pytest(invocation.Spawn, 'e_E', TestState.PASSED, '')
mock_pytest(invocation.Spawn, 'f_F', TestState.PASSED, '')
mock_pytest(invocation.Spawn, 'g_G', TestState.PASSED, '')
self.mocker.ReplayAll()
# [1] cannot run with [abd].
# Normal test 'aA' cannot run with normal test 'bB'.
self.goofy.run_once()
self.assertEquals(['aA'], [test.id for test in self.goofy.invocations])
self.goofy.wait()
# Normal test 'bB' cannot run with backgroundable test 'cC'.
self.goofy.run_once()
self.assertEquals(['bB'], [test.id for test in self.goofy.invocations])
self.goofy.wait()
# Normal test 'eE' cannot run with the combination of backgroundable
# test 'cC' and force_background test 'dD'.
self.goofy.run_once()
self.assertEquals(
set(['cC', 'dD']), set([test.id for test in self.goofy.invocations]))
self.goofy.wait()
# [2] cannot run with [a]
# Backgroundable test 'gG' cannot run with the normal test 'eE'.
self.goofy.run_once()
self.assertEquals(
set(['eE', 'fF']), set([test.id for test in self.goofy.invocations]))
self.goofy.wait()
self.goofy.run_once()
self.assertEquals(['gG'], [test.id for test in self.goofy.invocations])
self.goofy.wait()
self.mocker.VerifyAll()
self.mocker.ResetAll()
if __name__ == "__main__":
factory.init_logging('goofy_unittest')
goofy._inited_logging = True
goofy.suppress_chroot_warning = True
unittest.main()