Skip to content

Commit f5b4caf

Browse files
authored
[Diagnostic Logs] Followup for project-chip#31638 (project-chip#31833)
* [CI] Make it possible to specify the timeout when waiting for a specific string in scripts/tests/chiptest/test_definition.py since it may be longer than 10 seconds * [CI] Add a second instance of the configured applications such that YAML can start the app multiple times if needed * [BDX:DiagnosticLog] Update the server such that it supports multiple downloads in parallel * [darwin-framework-tool] Address post-landing comments of project-chip#31638 [CI] Add some tests for bdx download with darwin-framework-tool
1 parent 239ef48 commit f5b4caf

24 files changed

+1465
-298
lines changed

examples/chip-tool/py_matter_chip_tool_adapter/matter_chip_tool_adapter/encoder.py

+11
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,17 @@
148148
}
149149
},
150150

151+
'Bdx': {
152+
'commands': {
153+
'Download': {
154+
'arguments': {
155+
'LogType': 'log-type',
156+
},
157+
'has_endpoint': False,
158+
}
159+
}
160+
},
161+
151162
'DelayCommands': {
152163
'alias': 'delay',
153164
'commands': {

examples/darwin-framework-tool/commands/bdx/DownloadLogCommand.h

+10-1
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,23 @@ class DownloadLogCommand : public CHIPCommandBridge
3232
"The timeout for getting the log. If the timeout expires, completion will be called with whatever has been "
3333
"retrieved by that point (which might be none or a partial log). If the timeout is set to 0, the request will "
3434
"not expire and completion will not be called until the log is fully retrieved or an error occurs.");
35+
AddArgument("async", 0, 1, &mIsAsyncCommand,
36+
"By default the command waits for the download to finish before returning. If async is true the command will "
37+
"not wait and the download will proceed in the background");
38+
AddArgument("filepath", &mFilePath, "An optional filepath to save the download log content to.");
3539
}
3640

3741
/////////// CHIPCommandBridge Interface /////////
3842
CHIP_ERROR RunCommand() override;
39-
chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(10); }
43+
chip::System::Clock::Timeout GetWaitDuration() const override
44+
{
45+
return chip::System::Clock::Seconds16(mTimeout > 0 ? mTimeout + 10 : 300);
46+
}
4047

4148
private:
4249
chip::NodeId mNodeId;
4350
uint8_t mLogType;
4451
uint16_t mTimeout;
52+
chip::Optional<char *> mFilePath;
53+
chip::Optional<bool> mIsAsyncCommand;
4554
};

examples/darwin-framework-tool/commands/bdx/DownloadLogCommand.mm

+44-8
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#import "MTRError_Utils.h"
2222

2323
#include "DownloadLogCommand.h"
24+
#include "RemoteDataModelLogger.h"
2425

2526
CHIP_ERROR DownloadLogCommand::RunCommand()
2627
{
@@ -32,27 +33,62 @@
3233
auto logType = static_cast<MTRDiagnosticLogType>(mLogType);
3334
auto queue = dispatch_queue_create("com.chip.bdx.downloader", DISPATCH_QUEUE_SERIAL);
3435

36+
bool shouldWaitForDownload = !mIsAsyncCommand.ValueOr(false);
37+
mIsAsyncCommand.ClearValue();
38+
39+
bool dumpToFile = mFilePath.HasValue();
40+
auto * dumpFilePath = dumpToFile ? [NSString stringWithUTF8String:mFilePath.Value()] : nil;
41+
mFilePath.ClearValue();
42+
3543
auto * self = this;
3644
auto completion = ^(NSURL * url, NSError * error) {
3745
// A non-nil url indicates the presence of content, which can occur even in error scenarios like timeouts.
46+
NSString * logContent = nil;
3847
if (nil != url) {
3948
NSError * readError = nil;
4049
auto * data = [NSData dataWithContentsOfURL:url options:NSDataReadingUncached error:&readError];
41-
VerifyOrReturn(nil == readError, self->SetCommandExitStatus(MTRErrorToCHIPErrorCode(readError)));
50+
if (nil != readError) {
51+
if (shouldWaitForDownload) {
52+
self->SetCommandExitStatus(MTRErrorToCHIPErrorCode(readError));
53+
}
54+
return;
55+
}
56+
57+
logContent = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
58+
NSLog(@"Content: %@", logContent);
4259

43-
auto * content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
44-
NSLog(@"Content: %@", content);
60+
if (dumpToFile) {
61+
NSError * writeError = nil;
62+
auto * fileManager = [NSFileManager defaultManager];
63+
[fileManager copyItemAtPath:[url path] toPath:dumpFilePath error:&writeError];
64+
if (nil != writeError) {
65+
if (shouldWaitForDownload) {
66+
self->SetCommandExitStatus(MTRErrorToCHIPErrorCode(readError));
67+
}
68+
return;
69+
}
70+
}
4571
}
4672

47-
VerifyOrReturn(nil == error, self->SetCommandExitStatus(MTRErrorToCHIPErrorCode(error)));
73+
ChipLogProgress(chipTool, "Diagnostic logs transfer: %s", error ? "Error" : "Success");
74+
auto err = RemoteDataModelLogger::LogBdxDownload(logContent, error);
4875

49-
// The url is nil when there are no logs on the target device.
50-
if (nil == url) {
51-
NSLog(@"No logs has been found onto node 0x" ChipLogFormatX64, ChipLogValueX64(mNodeId));
76+
if (CHIP_NO_ERROR != err) {
77+
if (shouldWaitForDownload) {
78+
self->SetCommandExitStatus(err);
79+
}
80+
return;
81+
}
82+
83+
if (shouldWaitForDownload) {
84+
self->SetCommandExitStatus(MTRErrorToCHIPErrorCode(error));
5285
}
53-
self->SetCommandExitStatus(CHIP_NO_ERROR);
5486
};
5587

5688
[device downloadLogOfType:logType timeout:mTimeout queue:queue completion:completion];
89+
90+
if (!shouldWaitForDownload) {
91+
SetCommandExitStatus(CHIP_NO_ERROR);
92+
}
5793
return CHIP_NO_ERROR;
5894
}

examples/darwin-framework-tool/commands/common/RemoteDataModelLogger.h

+1
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@ CHIP_ERROR LogCommandAsJSON(NSNumber * endpointId, NSNumber * clusterId, NSNumbe
3232
CHIP_ERROR LogAttributeErrorAsJSON(NSNumber * endpointId, NSNumber * clusterId, NSNumber * attributeId, NSError * error);
3333
CHIP_ERROR LogCommandErrorAsJSON(NSNumber * endpointId, NSNumber * clusterId, NSNumber * commandId, NSError * error);
3434
CHIP_ERROR LogGetCommissionerNodeId(NSNumber * nodeId);
35+
CHIP_ERROR LogBdxDownload(NSString * content, NSError * error);
3536
void SetDelegate(RemoteDataModelLoggerDelegate * delegate);
3637
}; // namespace RemoteDataModelLogger

examples/darwin-framework-tool/commands/common/RemoteDataModelLogger.mm

+29
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
constexpr char kClusterErrorIdKey[] = "clusterError";
3636
constexpr char kValueKey[] = "value";
3737
constexpr char kNodeIdKey[] = "nodeId";
38+
constexpr char kLogContentIdKey[] = "logContent";
3839

3940
constexpr char kBase64Header[] = "base64:";
4041

@@ -204,5 +205,33 @@ CHIP_ERROR LogGetCommissionerNodeId(NSNumber * value)
204205
return gDelegate->LogJSON(valueStr.c_str());
205206
}
206207

208+
CHIP_ERROR LogBdxDownload(NSString * content, NSError * error)
209+
{
210+
VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR);
211+
212+
Json::Value rootValue;
213+
rootValue[kValueKey] = Json::Value();
214+
215+
Json::Value jsonValue;
216+
VerifyOrDie(CHIP_NO_ERROR == AsJsonValue(content, jsonValue));
217+
rootValue[kValueKey][kLogContentIdKey] = jsonValue;
218+
219+
if (error) {
220+
auto err = MTRErrorToCHIPErrorCode(error);
221+
auto status = chip::app::StatusIB(err);
222+
223+
#if CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT
224+
auto statusName = chip::Protocols::InteractionModel::StatusName(status.mStatus);
225+
rootValue[kValueKey][kErrorIdKey] = statusName;
226+
#else
227+
auto statusName = status.mStatus;
228+
rootValue[kValueKey][kErrorIdKey] = chip::to_underlying(statusName);
229+
#endif // CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT
230+
}
231+
232+
auto valueStr = JsonToString(rootValue);
233+
return gDelegate->LogJSON(valueStr.c_str());
234+
}
235+
207236
void SetDelegate(RemoteDataModelLoggerDelegate * delegate) { gDelegate = delegate; }
208237
}; // namespace RemoteDataModelLogger

scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/accessory_server_bridge.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import xmlrpc.client
1818

1919
_DEFAULT_KEY = 'default'
20+
_DEFAULT_WAIT_FOR_MESSAGE_TIMEOUT_SECONDS = 10
2021
_IP = '127.0.0.1'
2122
_PORT = 9000
2223

@@ -113,9 +114,11 @@ def factoryReset(request):
113114
def waitForMessage(request):
114115
register_key = _get_option(request, 'registerKey', _DEFAULT_KEY)
115116
message = _get_option(request, 'message')
117+
timeout_in_seconds = _get_option(
118+
request, 'timeoutInSeconds', _DEFAULT_WAIT_FOR_MESSAGE_TIMEOUT_SECONDS)
116119

117120
with xmlrpc.client.ServerProxy(_make_url(), allow_none=True) as proxy:
118-
proxy.waitForMessage(register_key, [message])
121+
proxy.waitForMessage(register_key, [message], timeout_in_seconds)
119122

120123
def createOtaImage(request):
121124
otaImageFilePath = _get_option(request, 'otaImageFilePath')

scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/delay_commands.py

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
<command source="client" code="3" name="WaitForMessage">
4141
<arg name="registerKey" type="char_string"/>
4242
<arg name="message" type="char_string"/>
43+
<arg name="timeoutInSeconds" type="int16u" optional="true"/>
4344
</command>
4445
</cluster>
4546
</configurator>

scripts/tests/chiptest/__init__.py

+18-6
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,13 @@ def _GetInDevelopmentTests() -> Set[str]:
160160
}
161161

162162

163+
def _GetChipToolUnsupportedTests() -> Set[str]:
164+
"""Tests that fail in chip-tool for some reason"""
165+
return {
166+
"TestDiagnosticLogsDownloadCommand", # chip-tool does not implement a bdx download command.
167+
}
168+
169+
163170
def _GetDarwinFrameworkToolUnsupportedTests() -> Set[str]:
164171
"""Tests that fail in darwin-framework-tool for some reason"""
165172
return {
@@ -258,6 +265,7 @@ def _GetChipReplUnsupportedTests() -> Set[str]:
258265
"Test_TC_RVCCLEANM_3_3.yaml", # chip-repl does not support EqualityCommands pseudo-cluster
259266
"Test_TC_BINFO_2_1.yaml", # chip-repl does not support EqualityCommands pseudo-cluster
260267
"TestDiagnosticLogs.yaml", # chip-repl does not implement a BDXTransferServerDelegate
268+
"TestDiagnosticLogsDownloadCommand.yaml", # chip-repl does not implement the bdx download command
261269
}
262270

263271

@@ -340,7 +348,7 @@ def tests_with_command(chip_tool: str, is_manual: bool):
340348
)
341349

342350

343-
def _AllFoundYamlTests(treat_repl_unsupported_as_in_development: bool, treat_dft_unsupported_as_in_development: bool, use_short_run_name: bool):
351+
def _AllFoundYamlTests(treat_repl_unsupported_as_in_development: bool, treat_dft_unsupported_as_in_development: bool, treat_chip_tool_unsupported_as_in_development: bool, use_short_run_name: bool):
344352
"""
345353
use_short_run_name should be true if we want the run_name to be "Test_ABC" instead of "some/path/Test_ABC.yaml"
346354
"""
@@ -350,7 +358,8 @@ def _AllFoundYamlTests(treat_repl_unsupported_as_in_development: bool, treat_dft
350358
extra_slow_tests = _GetExtraSlowTests()
351359
in_development_tests = _GetInDevelopmentTests()
352360
chip_repl_unsupported_tests = _GetChipReplUnsupportedTests()
353-
treat_dft_unsupported_as_in_development_tests = _GetDarwinFrameworkToolUnsupportedTests()
361+
dft_unsupported_as_in_development_tests = _GetDarwinFrameworkToolUnsupportedTests()
362+
chip_tool_unsupported_as_in_development_tests = _GetChipToolUnsupportedTests()
354363
purposeful_failure_tests = _GetPurposefulFailureTests()
355364

356365
for path in _AllYamlTests():
@@ -384,7 +393,10 @@ def _AllFoundYamlTests(treat_repl_unsupported_as_in_development: bool, treat_dft
384393
else:
385394
run_name = str(path)
386395

387-
if treat_dft_unsupported_as_in_development and run_name in treat_dft_unsupported_as_in_development_tests:
396+
if treat_dft_unsupported_as_in_development and run_name in dft_unsupported_as_in_development_tests:
397+
tags.add(TestTag.IN_DEVELOPMENT)
398+
399+
if treat_chip_tool_unsupported_as_in_development and run_name in chip_tool_unsupported_as_in_development_tests:
388400
tags.add(TestTag.IN_DEVELOPMENT)
389401

390402
yield TestDefinition(
@@ -396,17 +408,17 @@ def _AllFoundYamlTests(treat_repl_unsupported_as_in_development: bool, treat_dft
396408

397409

398410
def AllReplYamlTests():
399-
for test in _AllFoundYamlTests(treat_repl_unsupported_as_in_development=True, treat_dft_unsupported_as_in_development=False, use_short_run_name=False):
411+
for test in _AllFoundYamlTests(treat_repl_unsupported_as_in_development=True, treat_dft_unsupported_as_in_development=False, treat_chip_tool_unsupported_as_in_development=False, use_short_run_name=False):
400412
yield test
401413

402414

403415
def AllChipToolYamlTests():
404-
for test in _AllFoundYamlTests(treat_repl_unsupported_as_in_development=False, treat_dft_unsupported_as_in_development=False, use_short_run_name=True):
416+
for test in _AllFoundYamlTests(treat_repl_unsupported_as_in_development=False, treat_dft_unsupported_as_in_development=False, treat_chip_tool_unsupported_as_in_development=True, use_short_run_name=True):
405417
yield test
406418

407419

408420
def AllDarwinFrameworkToolYamlTests():
409-
for test in _AllFoundYamlTests(treat_repl_unsupported_as_in_development=False, treat_dft_unsupported_as_in_development=True, use_short_run_name=True):
421+
for test in _AllFoundYamlTests(treat_repl_unsupported_as_in_development=False, treat_dft_unsupported_as_in_development=True, treat_chip_tool_unsupported_as_in_development=False, use_short_run_name=True):
410422
yield test
411423

412424

scripts/tests/chiptest/accessories.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,12 @@ def factoryReset(self, name):
9898
return accessory.factoryReset()
9999
return False
100100

101-
def waitForMessage(self, name, message):
101+
def waitForMessage(self, name, message, timeoutInSeconds=10):
102102
accessory = self.__accessories[name]
103103
if accessory:
104104
# The message param comes directly from the sys.argv[2:] of WaitForMessage.py and should contain a list of strings that
105105
# comprise the entire message to wait for
106-
return accessory.waitForMessage(' '.join(message))
106+
return accessory.waitForMessage(' '.join(message), timeoutInSeconds)
107107
return False
108108

109109
def createOtaImage(self, otaImageFilePath, rawImageFilePath, rawImageContent, vid='0xDEAD', pid='0xBEEF'):

scripts/tests/chiptest/test_definition.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ def factoryReset(self):
8383
def waitForAnyAdvertisement(self):
8484
self.__waitFor("mDNS service published:", self.process, self.outpipe)
8585

86-
def waitForMessage(self, message):
87-
self.__waitFor(message, self.process, self.outpipe)
86+
def waitForMessage(self, message, timeoutInSeconds=10):
87+
self.__waitFor(message, self.process, self.outpipe, timeoutInSeconds)
8888
return True
8989

9090
def kill(self):
@@ -124,7 +124,7 @@ def __startServer(self, runner, command):
124124
self.kvsPathSet.add(value)
125125
return runner.RunSubprocess(app_cmd, name='APP ', wait=False)
126126

127-
def __waitFor(self, waitForString, server_process, outpipe):
127+
def __waitFor(self, waitForString, server_process, outpipe, timeoutInSeconds=10):
128128
logging.debug('Waiting for %s' % waitForString)
129129

130130
start_time = time.monotonic()
@@ -139,7 +139,7 @@ def __waitFor(self, waitForString, server_process, outpipe):
139139
(waitForString, server_process.returncode))
140140
logging.error(died_str)
141141
raise Exception(died_str)
142-
if time.monotonic() - start_time > 10:
142+
if time.monotonic() - start_time > timeoutInSeconds:
143143
raise Exception('Timeout while waiting for %s' % waitForString)
144144
time.sleep(0.1)
145145
ready, self.lastLogIndex = outpipe.CapturedLogContains(
@@ -336,6 +336,13 @@ def Run(self, runner, apps_register, paths: ApplicationPaths, pics_file: str,
336336
# so it will be commissionable again.
337337
app.factoryReset()
338338

339+
# It may sometimes be useful to run the same app multiple times depending
340+
# on the implementation. So this code creates a duplicate entry but with a different
341+
# key.
342+
app = App(runner, path)
343+
apps_register.add(f'{key}#2', app)
344+
app.factoryReset()
345+
339346
if dry_run:
340347
tool_storage_dir = None
341348
tool_storage_args = []

0 commit comments

Comments
 (0)