blob: 58712cbaad31ccaae3aacdc4846c7d03abee6210 [file] [log] [blame]
#
# Copyright 2023 The Cobalt Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for Raspi launcher"""
import logging
from starboard.raspi.shared import launcher
import sys
import argparse
import unittest
import os
from unittest.mock import patch, ANY, call, Mock
import tempfile
from pathlib import Path
import pexpect
# pylint: disable=missing-class-docstring
@unittest.skipIf(os.name == 'nt', 'Pexpect does not work on Windows')
class LauncherTest(unittest.TestCase):
def setUp(self):
self.target = 'baz'
self.device_id = '198.51.100.1' # Reserved address
# Current launcher requires real files, so we generate one
# pylint: disable=consider-using-with
self.tmpdir = tempfile.TemporaryDirectory()
target_path = os.path.join(self.tmpdir.name, 'install', self.target)
os.makedirs(target_path)
Path(os.path.join(target_path, self.target)).touch()
# Minimal set of params required to crete one
self.params = {
'device_id': self.device_id,
'platform': 'raspi-2',
'target_name': self.target,
'config': 'test',
'out_directory': self.tmpdir.name
}
self.fake_timeout = 0.11
# pylint: disable=protected-access
def _make_launcher(self):
launcher.Launcher._PEXPECT_TIMEOUT = self.fake_timeout
launcher.Launcher._PEXPECT_PASSWORD_TIMEOUT_MAX_RETRIES = 0
launcher.Launcher._PEXPECT_SHUTDOWN_SLEEP_TIME = 0.12
launcher.Launcher._INTER_COMMAND_DELAY_SECONDS = 0.013
launcher.Launcher._PEXPECT_READLINE_TIMEOUT_MAX_RETRIES = 2
launch = launcher.Launcher(**self.params)
return launch
class LauncherAPITest(LauncherTest):
def test_construct(self):
launch = self._make_launcher()
self.assertIsNotNone(launch)
self.assertEqual(launch.device_id, self.device_id)
self.assertEqual(launch.platform_name, 'raspi-2')
self.assertEqual(launch.target_name, self.target)
self.assertEqual(launch.config, 'test')
self.assertEqual(launch.out_directory, self.tmpdir.name)
def test_run(self):
result = self._make_launcher().Run()
# Expect test failure
self.assertEqual(result, 1)
def test_ip(self):
self.assertEqual(self._make_launcher().GetDeviceIp(), self.device_id)
def test_output(self):
# The path is hardcoded in the launcher
self.assertEqual(self._make_launcher().GetDeviceOutputPath(), '/tmp')
def test_kill(self):
self.assertIsNone(self._make_launcher().Kill())
class StringContains(str):
def __eq__(self, value):
return self in value
# Tests here test implementation details, rather than behavior.
# pylint: disable=protected-access
class LauncherInternalsTest(LauncherTest):
def setUp(self):
super().setUp()
self.launch = self._make_launcher()
self.launch.pexpect_process = Mock(
spec_set=['expect', 'sendline', 'readline'])
@patch('starboard.raspi.shared.launcher.pexpect.spawn')
def test_spawn(self, spawn):
mock_pexpect = spawn.return_value
self.launch._PexpectSpawnAndConnect('echo test')
spawn.assert_called_once_with('echo test', timeout=ANY, encoding=ANY)
mock_pexpect.sendline.assert_called_once_with(
'echo cobalt-launcher-login-success')
mock_pexpect.expect.assert_any_call(['cobalt-launcher-login-success'])
def test_sleep(self):
self.launch._Sleep(42)
self.launch.pexpect_process.sendline.assert_called_once_with(
'sleep 42;echo cobalt-launcher-done-sleeping')
self.launch.pexpect_process.expect.assert_called_once_with(
['cobalt-launcher-done-sleeping'])
def test_waitforconnect(self):
self.launch._WaitForPrompt()
self.launch.pexpect_process.expect.assert_called_once_with(
'pi@raspberrypi:')
# trigger one timeout
self.launch.pexpect_process.expect = Mock(
side_effect=[pexpect.TIMEOUT(1), None])
self.launch._WaitForPrompt()
self.launch.pexpect_process.expect.assert_has_calls([
call('pi@raspberrypi:'),
call('pi@raspberrypi:'),
])
# infinite timeout
self.launch.pexpect_process.expect = Mock(side_effect=pexpect.TIMEOUT(1))
with self.assertRaises(pexpect.TIMEOUT):
self.launch._WaitForPrompt()
def test_readlines(self):
# Return empty string
self.launch.pexpect_process.readline = Mock(return_value='')
self.launch._PexpectReadLines()
self.launch.pexpect_process.readline.assert_called_once()
self.assertIsNone(getattr(self.launch, 'return_value', None))
# Return default success tag
self.launch.pexpect_process.readline = Mock(
return_value=self.launch.test_complete_tag)
self.launch._PexpectReadLines()
self.launch.pexpect_process.readline.assert_called_once()
# This is a bug
self.assertIsNone(getattr(self.launch, 'return_value', None))
line = self.launch.test_complete_tag + self.launch.test_success_tag
self.launch.pexpect_process.readline = Mock(return_value=line)
self.launch._PexpectReadLines()
self.assertEqual(self.launch.return_value, 0)
self.launch.pexpect_process.readline = Mock(side_effect=pexpect.TIMEOUT(1))
with self.assertRaises(pexpect.TIMEOUT):
self.launch._PexpectReadLines()
def test_readlines_multiple(self):
self.launch.pexpect_process.readline = Mock(side_effect=['abc', 'bbc', ''])
self.launch._PexpectReadLines()
self.assertEqual(3, self.launch.pexpect_process.readline.call_count)
self.launch.pexpect_process.readline = Mock(
side_effect=['abc', 'bbc', '', 'none'])
self.launch._PexpectReadLines()
self.assertEqual(3, self.launch.pexpect_process.readline.call_count)
def test_kill_processes(self):
self.launch._KillExistingCobaltProcesses()
self.launch.pexpect_process.sendline.assert_any_call(
StringContains('pkill'))
@patch('starboard.raspi.shared.launcher.pexpect.spawn')
def test_run_with_mock(self, spawn):
pexpect_ = Mock()
pexpect_.readline = Mock(return_value='')
spawn.return_value = pexpect_
self.launch.Run()
self.assertEqual(self.launch.return_value, 1)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('device_id')
parser.add_argument('--target', default='eztime_test')
parser.add_argument('--out_directory')
parser.add_argument('--config', default='devel')
parser.add_argument('--verbose', '-v', action='store_true')
args = parser.parse_args()
logging.basicConfig(
stream=sys.stdout, level=logging.DEBUG if args.verbose else logging.INFO)
path = os.path.join(
os.path.dirname(launcher.__file__), f'../../../out/raspi-2_{args.config}')
logging.info('path: %s', path)
launch_test = launcher.Launcher(
platform='raspi-2',
target_name=args.target,
config=args.config,
device_id=args.device_id,
out_directory=path)
launch_test.Run()