blob: fcf9b3df385ea527e99dd5d6d1f226e61f9d31c8 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 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 cPickle as pickle
import logging
import math
import os
import subprocess
import threading
import time
import unittest
import mox
from mox import IgnoreArg
from ws4py.client import WebSocketBaseClient
import factory_common # pylint: disable=unused-import
from cros.factory.device import info as device_info
from cros.factory.goofy import goofy
from cros.factory.goofy.goofy import Goofy
from cros.factory.goofy import prespawner
from cros.factory.goofy.test_environment import Environment
from cros.factory.test import device_data
from cros.factory.test.env import goofy_proxy
from cros.factory.test.env import paths
from cros.factory.test.event import Event
from cros.factory.test import state
from cros.factory.test.state import TestState
from cros.factory.test.test_lists import manager
from cros.factory.utils import log_utils
from cros.factory.utils import net_utils
from cros.factory.utils import process_utils
from cros.factory.utils import type_utils
def MockPytest(name, test_state, error_msg, func=None):
"""Adds a side effect that a mock pytest will be executed.
Args:
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 SideEffect(info, unused_env):
assert info.pytest_name == name
if func:
func()
with open(info.results_path, 'w') as out:
pickle.dump((test_state, [(type_utils.Error(error_msg), [])]), out)
return process_utils.Spawn(['true'], stdout=subprocess.PIPE)
prespawner.Prespawner.spawn(
IgnoreArg(), IgnoreArg()).WithSideEffects(SideEffect)
class GoofyTest(unittest.TestCase):
"""Base class for Goofy test cases."""
test_list = {} # Overridden by subclasses
mock_goofy_server = True
mock_spawn_pytest = True
def setUp(self):
self.original_get_state_instance = state.GetInstance
# 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__)
goofy_proxy.DEFAULT_GOOFY_PORT = net_utils.FindUnusedTCPPort()
logging.info('Using port %d for factory state',
goofy_proxy.DEFAULT_GOOFY_PORT)
self.mocker = mox.Mox()
self.env = self.mocker.CreateMock(Environment)
self.state = state.StubFactoryState()
if self.mock_spawn_pytest:
self.mocker.StubOutWithMock(prespawner.Prespawner, 'spawn')
if self.mock_goofy_server:
self.mocker.StubOutClassWithMocks(goofy.goofy_server, 'GoofyServer')
state.GetInstance = lambda: self.state
self.mocker.StubOutWithMock(state, 'ClearState')
self.mocker.StubOutWithMock(state, 'FactoryState')
self.test_list_manager = self.mocker.CreateMock(manager.Manager)
self.BeforeInitGoofy()
self.RecordGoofyInit()
self.mocker.ReplayAll()
self.InitGoofy()
self.mocker.VerifyAll()
self.mocker.ResetAll()
self.AfterInitGoofy()
def tearDown(self):
try:
self.mocker.VerifyAll()
self.mocker.ResetAll()
self.RecordGoofyDestroy()
self.mocker.ReplayAll()
self.goofy.Destroy()
self.mocker.VerifyAll()
self.mocker.ResetAll()
# 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)
finally:
state.GetInstance = self.original_get_state_instance
self.mocker.UnsetStubs()
def InitGoofy(self, restart=True):
"""Initializes and returns a Goofy."""
new_goofy = Goofy()
args = []
if restart:
args.append('--restart')
logging.info('Running goofy with args %r', args)
# Overrides all dut info, so goofy don't try to get those infos which spend
# lots of time.
for prop in device_info._INFO_PROP_LIST: # pylint: disable=protected-access
new_goofy.dut.info.Overrides(prop, '')
new_goofy.dut.info.Overrides(device_data.NAME_MLB_SERIAL_NUMBER,
'mlb_sn_123456789')
new_goofy.dut.info.Overrides(device_data.NAME_SERIAL_NUMBER, 'sn_123456789')
new_goofy.test_list_manager = self.test_list_manager
new_goofy.Init(args, self.env or Environment())
self.goofy = new_goofy
def RecordGoofyInit(self, restart=True):
if restart:
state.ClearState()
state.FactoryState().AndReturn(self.state)
if self.mock_goofy_server:
server = goofy.goofy_server.GoofyServer((
goofy_proxy.DEFAULT_GOOFY_BIND,
goofy_proxy.DEFAULT_GOOFY_PORT))
# We do not use mox for server.serve_forever, since the method is run in
# another thread, and mox object are NOT thread safe.
server.serve_forever = lambda *args, **kwargs: None
server.RegisterPath('/',
os.path.join(paths.FACTORY_PYTHON_PACKAGE_DIR,
'goofy/static')).InAnyOrder()
server.RegisterData('/index.html', 'text/html', IgnoreArg()).InAnyOrder()
server.AddRPCInstance(goofy_proxy.STATE_URL, self.state).InAnyOrder()
server.AddHTTPGetHandler('/event', IgnoreArg()).InAnyOrder()
server.RegisterData('/js/goofy-translations.js', 'application/javascript',
IgnoreArg()).InAnyOrder()
server.RegisterData('/css/i18n.css', 'text/css', IgnoreArg()).InAnyOrder()
if self.test_list:
test_list = manager.BuildTestListForUnittest(
test_list_config=self.test_list)
test_list.options.read_device_data_from_vpd_on_init = False
self.test_list_manager.BuildAllTestLists().AndReturn(
({'test': test_list}, {}))
self.test_list_manager.GetActiveTestListId().AndReturn('test')
def RecordGoofyDestroy(self):
if self.mock_goofy_server:
self.goofy.goofy_server.shutdown()
self.goofy.goofy_server.server_close()
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 BeforeInitGoofy(self):
"""Hook invoked before InitGoofy."""
def AfterInitGoofy(self):
"""Hook invoked after InitGoofy."""
def CheckOneTest(self, test_id, name, passed, error_msg,
trigger=None, does_not_start=False, setup_mocks=True,
expected_count=1):
"""Runs a single pytest, waiting for it to complete.
Args:
test_id: The ID of the test expected to run.
name: The pytest name of the test expected to run.
passed: The TestState that 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 pytest. 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:
MockPytest(name, passed, error_msg)
self.mocker.ReplayAll()
if trigger:
trigger()
self.assertTrue(self.goofy.RunOnce())
self.assertEqual(
[] if does_not_start else [test_id],
[invoc.test.path for invoc in self.goofy.invocations.itervalues()])
self._Wait()
test_state = self.state.GetTestState(test_id)
self.assertEqual(passed, test_state.status)
self.assertEqual(0 if does_not_start else expected_count, test_state.count)
self.assertEqual(error_msg, test_state.error_msg)
class GoofyUITest(GoofyTest):
mock_goofy_server = False
def __init__(self, *args, **kwargs):
super(GoofyUITest, self).__init__(*args, **kwargs)
self.events = None
self.ws_start = None
self.ws_done = None
def BeforeInitGoofy(self):
# Keep a record of events we received
self.events = []
# Trigger this event once the web socket is ready
self.ws_start = threading.Event()
# Trigger this event once the web socket closes
self.ws_done = threading.Event()
def AfterInitGoofy(self):
class MyClient(WebSocketBaseClient):
"""The web socket client class."""
# pylint: disable=no-self-argument
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())
self.ws_start.set()
ws = MyClient('ws://%s:%d/event' %
(net_utils.LOCALHOST, goofy_proxy.DEFAULT_GOOFY_PORT),
protocols=None, extensions=None)
def OpenWebSocket():
ws.connect()
ws.run()
self.ws_done.set()
# After goofy.Init(), it should be ready to accept a web socket
process_utils.StartDaemonThread(target=OpenWebSocket)
def WaitForWebSocketStart(self):
self.ws_start.wait()
def WaitForWebSocketStop(self):
self.ws_done.wait()
# A simple test list with three tests.
ABC_TEST_LIST = [
{'id': 'a', 'pytest_name': 'a_A', },
{'id': 'b', 'pytest_name': 'b_B', },
{'id': 'c', 'pytest_name': 'c_C', },
]
class BasicTest(GoofyUITest):
"""A simple test case that checks that tests are run in the correct order."""
test_list = {
'tests': ABC_TEST_LIST
}
def runTest(self):
self.CheckOneTest('test:a', 'a_A', TestState.PASSED, '')
self.CheckOneTest('test:b', 'b_B', TestState.FAILED, 'Uh-oh')
self.CheckOneTest('test:c', 'c_C', TestState.FAILED, 'Uh-oh')
self.assertEqual(
dict(id=None, path='test:', subtests=[
dict(count=1, error_msg=None, id='a', path='test:a',
status='PASSED'),
dict(count=1, error_msg='Uh-oh', id='b', path='test:b',
status='FAILED'),
dict(count=1, error_msg='Uh-oh', id='c', path='test:c',
status='FAILED'),
]),
self.goofy.test_list.ToFactoryTestList().AsDict(
state.GetInstance().GetTestStates()))
class WebSocketTest(GoofyUITest):
"""A test case that checks the behavior of web sockets."""
test_list = {
'tests': ABC_TEST_LIST
}
def CheckTestStatusChange(self, test_id, test_state):
# The Goofy Server should receive the events in 2 seconds.
for unused_t in xrange(20):
statuses = []
for event in self.events:
if event.type == Event.Type.STATE_CHANGE and event.path == test_id:
statuses.append(event.state['status'])
if statuses == [TestState.UNTESTED, TestState.ACTIVE, test_state]:
return True
time.sleep(0.1)
return False
def runTest(self):
self.WaitForWebSocketStart()
self.CheckOneTest('test:a', 'a_A', TestState.PASSED, '')
self.assertTrue(self.CheckTestStatusChange('test:a', TestState.PASSED))
self.CheckOneTest('test:b', 'b_B', TestState.FAILED, 'Uh-oh')
self.assertTrue(self.CheckTestStatusChange('test:b', TestState.FAILED))
self.CheckOneTest('test:c', 'c_C', TestState.FAILED, 'Uh-oh')
self.assertTrue(self.CheckTestStatusChange('test:c', TestState.FAILED))
# Kill Goofy and wait for the web socket to close gracefully
self.RecordGoofyDestroy()
self.mocker.ReplayAll()
self.goofy.Destroy()
self.WaitForWebSocketStop()
hello_event = 0
for event in self.events:
if event.type == Event.Type.HELLO:
hello_event += 1
# There should be one hello event
self.assertEqual(1, hello_event)
# Check the statuses again.
self.assertTrue(self.CheckTestStatusChange('test:a', TestState.PASSED))
self.assertTrue(self.CheckTestStatusChange('test:b', TestState.FAILED))
self.assertTrue(self.CheckTestStatusChange('test:c', TestState.FAILED))
class ShutdownTest(GoofyUITest):
"""A test case that checks the behavior of shutdown."""
test_list = {
'tests': [
{'inherit': 'RebootStep', 'iterations': 3},
{'id': 'a', 'pytest_name': 'a_A'},
]
}
def runTest(self):
# Expect a reboot request.
MockPytest('shutdown', TestState.ACTIVE, '',
func=lambda: self.goofy.Shutdown('reboot'))
self.env.shutdown('reboot').AndReturn(True)
self.mocker.ReplayAll()
self.assertTrue(self.goofy.RunOnce())
self._Wait()
# That should have enqueued a task that will cause Goofy
# to shut down.
self.assertFalse(self.goofy.RunOnce())
# There should be a list of tests to run on wake-up.
test_list_iterator = self.state.DataShelfGetValue(
goofy.TESTS_AFTER_SHUTDOWN, optional=True)
self.assertEqual('test:RebootStep', test_list_iterator.Top().node)
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()
# Goofy should invoke shutdown test to do post-shutdown verification.
MockPytest('shutdown', TestState.PASSED, '')
# Goofy should invoke shutdown again to start next iteration.
MockPytest('shutdown', TestState.ACTIVE,
'', func=lambda: self.goofy.Shutdown('reboot'))
self.env.shutdown('reboot').AndReturn(True)
self.RecordGoofyDestroy()
self.RecordGoofyInit(restart=False)
self.mocker.ReplayAll()
self.goofy.Destroy()
self.BeforeInitGoofy()
self.InitGoofy(restart=False)
self.AfterInitGoofy()
self.goofy.RunOnce()
self._Wait()
# The third shutdown iteration.
self.mocker.ResetAll()
# Goofy should invoke shutdown test to do post-shutdown verification.
MockPytest('shutdown', TestState.PASSED, '')
self.RecordGoofyDestroy()
self.RecordGoofyInit(restart=False)
self.mocker.ReplayAll()
self.goofy.Destroy()
self.BeforeInitGoofy()
self.InitGoofy(restart=False)
self.AfterInitGoofy()
self.goofy.RunOnce()
self._Wait()
# No more shutdowns - now 'a' should run.
self.CheckOneTest('test:a', 'a_A', TestState.PASSED, '')
class RebootFailureTest(GoofyUITest):
"""A test case that checks the behavior of reboot failure."""
test_list = {
'tests': [
'RebootStep'
]
}
def runTest(self):
# Expect a reboot request
MockPytest('shutdown', TestState.ACTIVE, '',
func=lambda: self.goofy.Shutdown('reboot'))
self.env.shutdown('reboot').AndReturn(True)
self.mocker.ReplayAll()
self.assertTrue(self.goofy.RunOnce())
self._Wait()
# That should have enqueued a task that will cause Goofy
# to shut down.
self.mocker.ReplayAll()
self.assertFalse(self.goofy.RunOnce())
self._Wait()
# Something pretty close to the current time should be written
# as the shutdown time.
shutdown_time = self.state.DataShelfGetValue('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.RecordGoofyDestroy()
self.mocker.ReplayAll()
self.goofy.Destroy()
self.mocker.ResetAll()
# Mock a failed shutdown post-shutdown verification.
MockPytest('shutdown', TestState.FAILED, 'Reboot failed.')
self.RecordGoofyInit(restart=False)
self.mocker.ReplayAll()
self.BeforeInitGoofy()
self.InitGoofy(restart=False)
self.AfterInitGoofy()
self.goofy.RunOnce()
self._Wait()
test_state = state.GetInstance().GetTestState(path='test:RebootStep')
self.assertEqual(TestState.FAILED, test_state.status)
logging.info('%s', test_state.error_msg)
self.assertTrue(test_state.error_msg.startswith(
'Reboot failed.'))
class NoAutoRunTest(GoofyUITest):
"""A test case that checks the behavior when auto_run_on_start is False."""
test_list = {
'tests': ABC_TEST_LIST,
'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.RunOnce()
self.assertEqual({}, self.goofy.invocations)
self._Wait()
# Tell Goofy to run 'b'.
self.CheckOneTest(
'test:b', 'b_B', TestState.PASSED, '',
trigger=lambda: self.goofy.HandleEvent(
Event(Event.Type.RESTART_TESTS, path='b')))
def runTest(self):
self._runTestB()
# No more tests to run now.
self.mocker.ReplayAll()
self.goofy.RunOnce()
self.assertEqual({}, self.goofy.invocations)
class PyTestTest(GoofyUITest):
"""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 (exec_python).
"""
test_list = {
'tests': [
{
'id': 'a',
'pytest_name': 'exec_python',
'args': {
'script': 'assert "Tomato" == "Tomato"'
}
},
{
'id': 'b',
'pytest_name': 'exec_python',
'args': {
'script': 'assert "Pa-TAY-to" == "Pa-TAH-to", "TAY-TAH"'
}
}
]
}
mock_goofy_server = False
mock_spawn_pytest = False
def runTest(self):
self.goofy.RunOnce()
self.assertEqual(
['a'], [invoc.test.id for invoc in self.goofy.invocations.itervalues()])
self.goofy.Wait()
self.assertEqual(
TestState.PASSED,
state.GetInstance().GetTestState(path='test:a').status)
self.goofy.RunOnce()
self.assertEqual(
['b'], [invoc.test.id for invoc in self.goofy.invocations.itervalues()])
self.goofy.Wait()
failed_state = state.GetInstance().GetTestState(path='test:b')
self.assertEqual(TestState.FAILED, failed_state.status)
self.assertTrue(
'TAY-TAH' in failed_state.error_msg,
failed_state.error_msg)
class MultipleIterationsTest(GoofyUITest):
"""Tests running a test multiple times."""
test_list = {
'tests': [
{'id': 'a', 'pytest_name': 'a_A'},
{'id': 'b', 'pytest_name': 'b_B', 'iterations': 3},
{'id': 'c', 'pytest_name': 'c_C', 'iterations': 3},
{'id': 'd', 'pytest_name': 'd_D'},
]
}
def runTest(self):
self.CheckOneTest('test:a', 'a_A', TestState.PASSED, '')
MockPytest('b_B', TestState.PASSED, '')
MockPytest('b_B', TestState.PASSED, '')
MockPytest('b_B', TestState.PASSED, '')
self.CheckOneTest('test:b', 'b_B', TestState.PASSED,
'', setup_mocks=False, expected_count=3)
MockPytest('c_C', TestState.PASSED, '')
MockPytest('c_C', TestState.FAILED, 'I bent my wookie')
# iterations=3, but it should stop after the first failed iteration.
self.CheckOneTest('test:c', 'c_C', TestState.FAILED,
'I bent my wookie', setup_mocks=False, expected_count=2)
self.CheckOneTest('test:d', 'd_D', TestState.PASSED, '')
class RequireRunTest(GoofyUITest):
"""Tests FactoryTest require_run argument."""
test_list = {
'options': {
'auto_run_on_start': False
},
'tests': [
{'id': 'a', 'pytest_name': 'a_A'},
{'id': 'b', 'pytest_name': 'b_B', 'require_run': 'a'},
]
}
def runTest(self):
self.goofy.RestartTests(
root=self.goofy.test_list.LookupPath('b'))
self.CheckOneTest('test:b', 'b_B', TestState.FAILED,
'Required tests [test:a] have not been run yet',
does_not_start=True)
self.goofy.RestartTests()
self.CheckOneTest('test:a', 'a_A', TestState.PASSED, '')
self.CheckOneTest('test:b', 'b_B', TestState.PASSED, '')
class StopOnFailureTest(GoofyUITest):
"""A unittest that checks if the goofy will stop after a test fails."""
test_list = {
'options': {
'auto_run_on_start': True,
'stop_on_failure': True
},
'tests': ABC_TEST_LIST
}
def runTest(self):
MockPytest('a_A', TestState.PASSED, '')
MockPytest('b_B', TestState.FAILED, 'Oops!')
self.mocker.ReplayAll()
# Make sure events are all processed.
for _ in range(3):
self.assertTrue(self.goofy.RunOnce())
self.goofy.Wait()
state_instance = state.GetInstance()
self.assertEqual(
[TestState.PASSED, TestState.FAILED, TestState.UNTESTED],
[state_instance.GetTestState(x).status
for x in ['test:a', 'test:b', 'test:c']])
self._Wait()
class ParallelTest(GoofyUITest):
"""A test for parallel tests, goofy should run them in parallel."""
test_list = {
'tests': [
{
'id': 'parallel',
'parallel': True,
'subtests': [
{'id': 'a', 'pytest_name': 'a_A'},
{'id': 'b', 'pytest_name': 'b_B'},
{'id': 'c', 'pytest_name': 'c_C'},
]
}
]
}
def runTest(self):
MockPytest('a_A', TestState.PASSED, '')
MockPytest('b_B', TestState.PASSED, '')
MockPytest('c_C', TestState.PASSED, '')
self.mocker.ReplayAll()
self.goofy.RunOnce()
self.assertEqual(
{'a', 'b', 'c'},
{invoc.test.id for invoc in self.goofy.invocations.itervalues()})
self.goofy.Wait()
self.mocker.VerifyAll()
self.mocker.ResetAll()
class WaivedTestTest(GoofyUITest):
"""A test to verify that a waived test does not block test list execution."""
test_list = {
'options': {
'auto_run_on_start': True,
'stop_on_failure': True,
'phase': 'PROTO',
'waived_tests': {
'PROTO': ['waived', 'G']
},
},
'tests': [
{'id': 'waived', 'pytest_name': 'waived_test'},
{'id': 'normal', 'pytest_name': 'normal_test'},
{
'id': 'G',
'subtests': [
{'id': 'waived', 'pytest_name': 'waived_test'},
]
}
]
}
def BeforeInitGoofy(self):
super(WaivedTestTest, self).BeforeInitGoofy()
# 'G.waived' is already FAILED previously.
self.state.UpdateTestState(path='test:G.waived', status=TestState.FAILED)
def runTest(self):
MockPytest('waived_test', TestState.FAILED, 'Failed')
MockPytest('normal_test', TestState.PASSED, '')
self.mocker.ReplayAll()
# After Goofy init, 'G.waived' should be set to 'FAILED_AND_WAIVED'.
self.assertEqual(self.state.GetTestState(path='test:G.waived').status,
TestState.FAILED_AND_WAIVED)
for unused_i in range(4):
self.assertTrue(self.goofy.RunOnce())
self.goofy.Wait()
self.assertEqual(
[TestState.FAILED_AND_WAIVED, TestState.PASSED,
TestState.FAILED_AND_WAIVED, TestState.FAILED_AND_WAIVED],
[self.state.GetTestState(x).status
for x in ['test:waived', 'test:normal', 'test:G', 'test:G.waived']])
self._Wait()
class SkippedTestTest(GoofyUITest):
"""A test to verify that a waived test does not block test list execution."""
test_list = {
'constants': {'has_a2': True},
'options': {
'auto_run_on_start': True,
'stop_on_failure': True,
'phase': 'PROTO',
'skipped_tests': {
'PROTO': ['skipped'],
'not device.has_a': ['*.A'],
'constants.has_a2': ['*.A_2']
}
},
'tests': [
{'id': 'skipped', 'pytest_name': 'normal_test'},
{'id': 'G',
'subtests': [
# This is skipped because device.has_a is not set
{'id': 'A', 'pytest_name': 'normal_test'},
# This is skipped because constants.has_a2 is True
{'id': 'A', 'pytest_name': 'normal_test'},
# This will be A_3, and it should not be skipped
{'id': 'A', 'pytest_name': 'normal_test'},
]},
{'id': 'normal', 'pytest_name': 'normal_test'},
],
}
def runTest(self):
MockPytest('normal_test', TestState.PASSED, '')
MockPytest('normal_test', TestState.PASSED, '')
self.mocker.ReplayAll()
for _ in range(4):
self.assertTrue(self.goofy.RunOnce())
self.goofy.Wait()
state_instance = state.GetInstance()
self.assertEqual(
[TestState.SKIPPED, TestState.SKIPPED, TestState.SKIPPED,
TestState.PASSED, TestState.PASSED],
[state_instance.GetTestState(x).status
for x in ['test:skipped', 'test:G.A', 'test:G.A_2', 'test:G.A_3',
'test:normal']])
self._Wait()
class EndlessLoopTest(GoofyUITest):
"""A test to verify that a waived test does not block test list execution."""
test_list = {
'options': {'auto_run_on_start': True},
'tests': [
{'id': 'G',
'iterations': -1,
'retries': -1,
'subtests': [
{'id': 'A', 'pytest_name': 'normal_test'}
]}
]
}
def runTest(self):
for unused_i in xrange(4):
MockPytest('normal_test', TestState.PASSED, '')
for unused_i in xrange(4):
MockPytest('normal_test', TestState.FAILED, '')
self.mocker.ReplayAll()
state_instance = state.GetInstance()
for i in range(8):
self.assertTrue(self.goofy.RunOnce())
self.goofy.Wait()
self.assertEqual(
state_instance.GetTestState(path='test:G').iterations_left,
float('inf'))
self.assertEqual(
state_instance.GetTestState(path='test:G').retries_left,
float('inf'))
self.assertEqual(state_instance.GetTestState(path='test:G.A').count,
i + 1)
self.assertEqual(state_instance.GetTestState(path='test:G.A').status,
TestState.PASSED if i < 4 else TestState.FAILED)
self._Wait()
class NoHostTest(GoofyUITest):
"""A test to verify that tests marked 'no_host' run without host UI."""
test_list = {
'tests': [
{'id': 'a', 'pytest_name': 'exec_python', 'no_host': True,
'args': {'script': 'assert "Tomato" == "Tomato"'}},
{'id': 'b', 'pytest_name': 'exec_python', 'no_host': False,
'args': {'script': 'assert "Tomato" == "Tomato"'}},
]
}
mock_spawn_pytest = False
def runTest(self):
self.mocker.StubOutWithMock(self.goofy, 'InitUI')
# No UI for test 'a', should not call InitUI
self.mocker.ReplayAll()
self.goofy.RunOnce()
self._Wait()
self.assertEqual(
TestState.PASSED,
state.GetInstance().GetTestState(path='test:a').status)
# Start the UI for test 'b'
self.goofy.InitUI()
self.mocker.ReplayAll()
self.goofy.RunOnce()
self._Wait()
self.assertEqual(
TestState.PASSED,
state.GetInstance().GetTestState(path='test:b').status)
if __name__ == '__main__':
log_utils.InitLogging()
goofy.suppress_chroot_warning = True
unittest.main()