Skip to content

Commit

Permalink
Add CI for darwin-framework-tool acting as OTA provider. (#25851)
Browse files Browse the repository at this point in the history
  • Loading branch information
bzbarsky-apple authored Mar 28, 2023
1 parent 1f7e4a1 commit a5e9259
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 9 deletions.
13 changes: 13 additions & 0 deletions .github/workflows/darwin-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,19 @@ jobs:
--tv-app ./out/darwin-x64-tv-app-${BUILD_VARIANT}/chip-tv-app \
--bridge-app ./out/darwin-x64-bridge-${BUILD_VARIANT}/chip-bridge-app \
"
- name: Run OTA Test
timeout-minutes: 5
run: |
./scripts/run_in_build_env.sh \
"./scripts/tests/run_darwin_framework_ota_test.py \
run \
--darwin-framework-tool ./out/darwin-x64-darwin-framework-tool-${BUILD_VARIANT}/darwin-framework-tool \
--ota-requestor-app ./out/darwin-x64-ota-requestor-${BUILD_VARIANT}/chip-ota-requestor-app \
--ota-data-file /tmp/rawImage \
--ota-image-file /tmp/otaImage \
--ota-destination-file /tmp/downloadedImage \
--ota-candidate-file /tmp/otaCandidateJSON \
"
- name: Uploading core files
uses: actions/upload-artifact@v3
if: ${{ failure() && !env.ACT }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ class Commands;
class InteractiveStartCommand : public CHIPCommandBridge
{
public:
InteractiveStartCommand(Commands * commandsHandler) : CHIPCommandBridge("start"), mHandler(commandsHandler) {}
InteractiveStartCommand(Commands * commandsHandler) : CHIPCommandBridge("start"), mHandler(commandsHandler)
{
AddArgument(
"additional-prompt", &mAdditionalPrompt,
"Force printing of an additional prompt that can then be detected by something trying to script interactive mode");
}

CHIP_ERROR RunCommand() override;

Expand All @@ -39,4 +44,5 @@ class InteractiveStartCommand : public CHIPCommandBridge
private:
bool ParseCommand(char * command);
Commands * mHandler = nullptr;
chip::Optional<char *> mAdditionalPrompt;
};
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,18 @@ void ENFORCE_FORMAT(3, 0) LoggingCallback(const char * module, uint8_t category,
}
} // namespace

char * GetCommand(char * command)
char * GetCommand(const chip::Optional<char *> & mAdditionalPrompt, char * command)
{
if (command != nullptr) {
free(command);
command = nullptr;
}

if (mAdditionalPrompt.HasValue()) {
ClearLine();
printf("%s\n", mAdditionalPrompt.Value());
ClearLine();
}
command = readline(kInteractiveModePrompt);

// Do not save empty lines
Expand Down Expand Up @@ -118,7 +123,7 @@ el_status_t StopFunction()

char * command = nullptr;
while (YES) {
command = GetCommand(command);
command = GetCommand(mAdditionalPrompt, command);
if (command != nullptr && !ParseCommand(command)) {
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
VerifyOrReturnError(nil != controller, CHIP_ERROR_INCORRECT_STATE);

auto id = [controller.controllerNodeId unsignedLongLongValue];
ChipLogProgress(chipTool, "Commissioner Node Id 0x:" ChipLogFormatX64, ChipLogValueX64(id));
ChipLogProgress(chipTool, "Commissioner Node Id 0x" ChipLogFormatX64, ChipLogValueX64(id));

SetCommandExitStatus(CHIP_NO_ERROR);
return CHIP_NO_ERROR;
Expand Down
4 changes: 2 additions & 2 deletions scripts/tests/chiptest/accessories.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,14 @@ def waitForMessage(self, name, message):
return accessory.waitForMessage(' '.join(message))
return False

def createOtaImage(self, otaImageFilePath, rawImageFilePath, rawImageContent):
def createOtaImage(self, otaImageFilePath, rawImageFilePath, rawImageContent, vid='0xDEAD', pid='0xBEEF'):
# Write the raw image content
with open(rawImageFilePath, 'w') as rawFile:
rawFile.write(rawImageContent)

# Add an OTA header to the raw file
otaImageTool = _DEFAULT_CHIP_ROOT + '/src/app/ota_image_tool.py'
cmd = [otaImageTool, 'create', '-v', '0xDEAD', '-p', '0xBEEF', '-vn', '2',
cmd = [otaImageTool, 'create', '-v', vid, '-p', pid, '-vn', '2',
'-vs', "2.0", '-da', 'sha256', rawImageFilePath, otaImageFilePath]
s = subprocess.Popen(cmd)
s.wait()
Expand Down
6 changes: 3 additions & 3 deletions scripts/tests/chiptest/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ class Runner:
def __init__(self, capture_delegate=None):
self.capture_delegate = capture_delegate

def RunSubprocess(self, cmd, name, wait=True, dependencies=[], timeout_seconds: typing.Optional[int] = None):
def RunSubprocess(self, cmd, name, wait=True, dependencies=[], timeout_seconds: typing.Optional[int] = None, stdin=None):
outpipe = LogPipe(
logging.DEBUG, capture_delegate=self.capture_delegate,
name=name + ' OUT')
Expand All @@ -133,12 +133,12 @@ def RunSubprocess(self, cmd, name, wait=True, dependencies=[], timeout_seconds:

if sys.platform == 'darwin':
# Try harder to avoid any stdout buffering in our tests
cmd = ['stdbuf', '-o0'] + cmd
cmd = ['stdbuf', '-o0', '-i0'] + cmd

if self.capture_delegate:
self.capture_delegate.Log(name, 'EXECUTING %r' % cmd)

s = subprocess.Popen(cmd, stdout=outpipe, stderr=errpipe)
s = subprocess.Popen(cmd, stdin=stdin, stdout=outpipe, stderr=errpipe)
outpipe.close()
errpipe.close()

Expand Down
188 changes: 188 additions & 0 deletions scripts/tests/run_darwin_framework_ota_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
#! /usr/bin/env -S python3 -B

import io
import json
import logging
import time
from subprocess import PIPE

import click
from chiptest.accessories import AppsRegister
from chiptest.runner import Runner
from chiptest.test_definition import App, ExecutionCapture
from yaml.paths_finder import PathsFinder

TEST_NODE_ID = '0x12344321'
TEST_VID = '0xFFF1'
TEST_PID = '0x8001'


class DarwinToolRunner:
def __init__(self, runner, command):
self.process = None
self.outpipe = None
self.runner = runner
self.lastLogIndex = 0
self.command = command
self.stdin = None

def start(self):
self.process, self.outpipe, errpipe = self.runner.RunSubprocess(self.command,
name='DARWIN-TOOL',
wait=False,
stdin=PIPE)
self.stdin = io.TextIOWrapper(self.process.stdin, line_buffering=True)

def stop(self):
if self.process:
self.process.kill()

def waitForMessage(self, message):
logging.debug('Waiting for %s' % message)

start_time = time.monotonic()
ready, self.lastLogIndex = self.outpipe.CapturedLogContains(
message, self.lastLogIndex)
while not ready:
if self.process.poll() is not None:
died_str = ('Process died while waiting for %s, returncode %d' %
(message, self.process.returncode))
logging.error(died_str)
raise Exception(died_str)
if time.monotonic() - start_time > 10:
raise Exception('Timeout while waiting for %s' % message)
time.sleep(0.1)
ready, self.lastLogIndex = self.outpipe.CapturedLogContains(
message, self.lastLogIndex)

logging.debug('Success waiting for: %s' % message)


class InteractiveDarwinTool(DarwinToolRunner):
def __init__(self, runner, binary_path):
self.prompt = "WAITING FOR COMMANDS NOW"
super().__init__(runner, [binary_path, "interactive", "start", "--additional-prompt", self.prompt])

def waitForPrompt(self):
self.waitForMessage(self.prompt)

def sendCommand(self, command):
logging.debug('Sending command %s' % command)
print(command, file=self.stdin)
self.waitForPrompt()


@click.group(chain=True)
@click.pass_context
def main(context):
pass


@main.command(
'run', help='Execute the test')
@click.option(
'--darwin-framework-tool',
help="what darwin-framework-tool to use")
@click.option(
'--ota-requestor-app',
help='what ota requestor app to use')
@click.option(
'--ota-data-file',
required=True,
help='The file to use to store our OTA data. This file does not need to exist.')
@click.option(
'--ota-image-file',
required=True,
help='The file to use to store the OTA image we plan to send. This file does not need to exist.')
@click.option(
'--ota-destination-file',
required=True,
help='The destination file to use for the requestor\'s download. This file does not need to exist.')
@click.option(
'--ota-candidate-file',
required=True,
help='The file to use for our OTA candidate JSON. This file does not need to exist.')
@click.pass_context
def cmd_run(context, darwin_framework_tool, ota_requestor_app, ota_data_file, ota_image_file, ota_destination_file, ota_candidate_file):
paths_finder = PathsFinder()

if darwin_framework_tool is None:
darwin_framework_tool = paths_finder.get('darwin-framework-tool')
if ota_requestor_app is None:
ota_requestor_app = paths_finder.get('chip-ota-requestor-app')

runner = Runner()
runner.capture_delegate = ExecutionCapture()

apps_register = AppsRegister()
apps_register.init()

darwin_tool = None

try:
apps_register.createOtaImage(ota_image_file, ota_data_file, "This is some test OTA data", vid=TEST_VID, pid=TEST_PID)
json_data = {
"deviceSoftwareVersionModel": [{
"vendorId": int(TEST_VID, 16),
"productId": int(TEST_PID, 16),
"softwareVersion": 2,
"softwareVersionString": "2.0",
"cDVersionNumber": 18,
"softwareVersionValid": True,
"minApplicableSoftwareVersion": 0,
"maxApplicableSoftwareVersion": 100,
"otaURL": ota_image_file
}]
}
with open(ota_candidate_file, "w") as f:
json.dump(json_data, f)

requestor_app = App(runner, [ota_requestor_app, '--otaDownloadPath', ota_destination_file])
apps_register.add('default', requestor_app)

requestor_app.start()

pairing_cmd = [darwin_framework_tool, 'pairing', 'code', TEST_NODE_ID, requestor_app.setupCode]
runner.RunSubprocess(pairing_cmd, name='PAIR', dependencies=[apps_register])

# pairing get-commissioner-node-id does not seem to work right in interactive mode for some reason
darwin_tool = DarwinToolRunner(runner, [darwin_framework_tool, 'pairing', 'get-commissioner-node-id'])
darwin_tool.start()
darwin_tool.waitForMessage(": Commissioner Node Id")
nodeIdLine = darwin_tool.outpipe.FindLastMatchingLine('.*: Commissioner Node Id (0x[0-9A-F]+)')
if not nodeIdLine:
raise Exception("Unable to find commissioner node id")
commissionerNodeId = nodeIdLine.group(1)
darwin_tool.stop()

darwin_tool = InteractiveDarwinTool(runner, darwin_framework_tool)
darwin_tool.start()

darwin_tool.waitForPrompt()

darwin_tool.sendCommand("otasoftwareupdateapp candidate-file-path %s" % ota_candidate_file)
darwin_tool.sendCommand("otasoftwareupdateapp set-reply-params --status 0")
darwin_tool.sendCommand("otasoftwareupdaterequestor announce-otaprovider %s 0 0 0 %s 0" %
(commissionerNodeId, TEST_NODE_ID))

# Now wait for the OTA download to finish.
requestor_app.waitForMessage("OTA image downloaded to %s" % ota_destination_file)

# Make sure the right thing was downloaded.
apps_register.compareFiles(ota_data_file, ota_destination_file)

except Exception:
logging.error("!!!!!!!!!!!!!!!!!!!! ERROR !!!!!!!!!!!!!!!!!!!!!!")
runner.capture_delegate.LogContents()
raise
finally:
if darwin_tool is not None:
darwin_tool.stop()
apps_register.killAll()
apps_register.factoryResetAll()
apps_register.removeAll()
apps_register.uninit()


if __name__ == '__main__':
main(auto_envvar_prefix='CHIP')

0 comments on commit a5e9259

Please sign in to comment.