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

Add CI for darwin-framework-tool acting as OTA provider. #25851

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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')