Skip to content

Commit ef774e8

Browse files
yufengwangcapull[bot]
authored andcommitted
[CI] Add Java pairing OnNetworkLong test (#23816)
1 parent 375f882 commit ef774e8

File tree

5 files changed

+312
-0
lines changed

5 files changed

+312
-0
lines changed

.github/workflows/tests.yaml

+12
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,18 @@ jobs:
462462
--target linux-x64-java-matter-controller \
463463
build \
464464
"
465+
- name: Run Tests
466+
timeout-minutes: 65
467+
run: |
468+
scripts/run_in_build_env.sh \
469+
'./scripts/tests/run_java_test.py \
470+
--app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app \
471+
--app-args "--discriminator 3840 --interface-id -1" \
472+
--tool-path out/linux-x64-java-matter-controller \
473+
--tool-cluster "pairing" \
474+
--tool-args "--nodeid 1 --setup-payload 20202021 --discriminator 3840 -t 1000" \
475+
--factoryreset \
476+
'
465477
- name: Uploading core files
466478
uses: actions/upload-artifact@v3
467479
if: ${{ failure() && !env.ACT }}

examples/java-matter-controller/java/src/com/matter/controller/commands/common/CommandManager.java

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ public final void run(String[] args) {
9797
command.initArguments(temp.length, temp);
9898
command.run();
9999
} catch (IllegalArgumentException e) {
100+
System.out.println("Run command failed with exception: " + e.getMessage());
100101
showCommand(args[0], command);
101102
} catch (Exception e) {
102103
System.out.println("Run command failed with exception: " + e.getMessage());

scripts/tests/java/base.py

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/usr/bin/env python3
2+
3+
#
4+
# Copyright (c) 2022 Project CHIP Authors
5+
# All rights reserved.
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
#
19+
20+
# Commissioning test.
21+
import logging
22+
import os
23+
import sys
24+
import queue
25+
import datetime
26+
import asyncio
27+
import threading
28+
import typing
29+
import time
30+
import subprocess
31+
from colorama import Fore, Style
32+
33+
34+
def EnqueueLogOutput(fp, tag, q):
35+
for line in iter(fp.readline, b''):
36+
timestamp = time.time()
37+
if len(line) > len('[1646290606.901990]') and line[0:1] == b'[':
38+
try:
39+
timestamp = float(line[1:18].decode())
40+
line = line[19:]
41+
except Exception as ex:
42+
pass
43+
sys.stdout.buffer.write(
44+
(f"[{datetime.datetime.fromtimestamp(timestamp).isoformat(sep=' ')}]").encode() + tag + line)
45+
sys.stdout.flush()
46+
fp.close()
47+
48+
49+
def RedirectQueueThread(fp, tag, queue) -> threading.Thread:
50+
log_queue_thread = threading.Thread(target=EnqueueLogOutput, args=(
51+
fp, tag, queue))
52+
log_queue_thread.start()
53+
return log_queue_thread
54+
55+
56+
def DumpProgramOutputToQueue(thread_list: typing.List[threading.Thread], tag: str, process: subprocess.Popen, queue: queue.Queue):
57+
thread_list.append(RedirectQueueThread(process.stdout,
58+
(f"[{tag}][{Fore.YELLOW}STDOUT{Style.RESET_ALL}]").encode(), queue))
59+
thread_list.append(RedirectQueueThread(process.stderr,
60+
(f"[{tag}][{Fore.RED}STDERR{Style.RESET_ALL}]").encode(), queue))
+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/usr/bin/env python3
2+
3+
#
4+
# Copyright (c) 2022 Project CHIP Authors
5+
# All rights reserved.
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
#
19+
20+
# Commissioning test.
21+
import logging
22+
import os
23+
import sys
24+
import asyncio
25+
import queue
26+
import subprocess
27+
import threading
28+
import typing
29+
from optparse import OptionParser
30+
from colorama import Fore, Style
31+
from java.base import DumpProgramOutputToQueue
32+
33+
34+
class CommissioningTest:
35+
def __init__(self, thread_list: typing.List[threading.Thread], queue: queue.Queue, cmd: [], args: str):
36+
self.thread_list = thread_list
37+
self.queue = queue
38+
self.command = cmd
39+
40+
optParser = OptionParser()
41+
optParser.add_option(
42+
"-t",
43+
"--timeout",
44+
action="store",
45+
dest="testTimeout",
46+
default='200',
47+
type='str',
48+
help="The program will return with timeout after specified seconds.",
49+
metavar="<timeout-second>",
50+
)
51+
optParser.add_option(
52+
"-a",
53+
"--address",
54+
action="store",
55+
dest="deviceAddress",
56+
default='',
57+
type='str',
58+
help="Address of the device",
59+
metavar="<device-addr>",
60+
)
61+
optParser.add_option(
62+
"--setup-payload",
63+
action="store",
64+
dest="setupPayload",
65+
default='',
66+
type='str',
67+
help="Setup Payload (manual pairing code or QR code content)",
68+
metavar="<setup-payload>"
69+
)
70+
optParser.add_option(
71+
"--nodeid",
72+
action="store",
73+
dest="nodeid",
74+
default='1',
75+
type='str',
76+
help="The Node ID issued to the device",
77+
metavar="<nodeid>"
78+
)
79+
optParser.add_option(
80+
"--discriminator",
81+
action="store",
82+
dest="discriminator",
83+
default='3840',
84+
type='str',
85+
help="Discriminator of the device",
86+
metavar="<nodeid>"
87+
)
88+
optParser.add_option(
89+
"-p",
90+
"--paa-trust-store-path",
91+
action="store",
92+
dest="paaTrustStorePath",
93+
default='',
94+
type='str',
95+
help="Path that contains valid and trusted PAA Root Certificates.",
96+
metavar="<paa-trust-store-path>"
97+
)
98+
99+
(options, remainingArgs) = optParser.parse_args(args.split())
100+
101+
self.nodeid = options.nodeid
102+
self.setupPayload = options.setupPayload
103+
self.discriminator = options.discriminator
104+
self.testTimeout = options.testTimeout
105+
106+
logging.basicConfig(level=logging.INFO)
107+
108+
def TestOnnetworkLong(self, nodeid, setuppin, discriminator, timeout):
109+
java_command = self.command + ['pairing', 'onnetwork-long', nodeid, setuppin, discriminator, timeout]
110+
print(java_command)
111+
logging.info(f"Execute: {java_command}")
112+
java_process = subprocess.Popen(
113+
java_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
114+
DumpProgramOutputToQueue(self.thread_list, Fore.GREEN + "JAVA " + Style.RESET_ALL, java_process, self.queue)
115+
return java_process.wait()
116+
117+
def RunTest(self):
118+
logging.info("Testing onnetwork-long pairing")
119+
java_exit_code = self.TestOnnetworkLong(self.nodeid, self.setupPayload, self.discriminator, self.testTimeout)
120+
if java_exit_code != 0:
121+
logging.error("Testing onnetwork-long pairing failed with error %r" % java_exit_code)
122+
return java_exit_code
123+
124+
# Testing complete without errors
125+
return 0

scripts/tests/run_java_test.py

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#!/usr/bin/env -S python3 -B
2+
3+
# Copyright (c) 2022 Project CHIP Authors
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
import click
18+
import coloredlogs
19+
import logging
20+
import os
21+
import pathlib
22+
import pty
23+
import queue
24+
import re
25+
import shlex
26+
import signal
27+
import subprocess
28+
import sys
29+
from java.base import DumpProgramOutputToQueue
30+
from java.commissioning_test import CommissioningTest
31+
from colorama import Fore, Style
32+
33+
34+
@click.command()
35+
@click.option("--app", type=click.Path(exists=True), default=None, help='Path to local application to use, omit to use external apps.')
36+
@click.option("--app-args", type=str, default='', help='The extra arguments passed to the device.')
37+
@click.option("--tool-path", type=click.Path(exists=True), default=None, help='Path to java-matter-controller.')
38+
@click.option("--tool-cluster", type=str, default='pairing', help='The cluster name passed to the java-matter-controller.')
39+
@click.option("--tool-args", type=str, default='', help='The arguments passed to the java-matter-controller.')
40+
@click.option("--factoryreset", is_flag=True, help='Remove app configs (/tmp/chip*) before running the tests.')
41+
def main(app: str, app_args: str, tool_path: str, tool_cluster: str, tool_args: str, factoryreset: bool):
42+
logging.info("Execute: {script_command}")
43+
44+
if factoryreset:
45+
# Remove native app config
46+
retcode = subprocess.call("rm -rf /tmp/chip*", shell=True)
47+
if retcode != 0:
48+
raise Exception("Failed to remove /tmp/chip* for factory reset.")
49+
50+
print("Contents of test directory: %s" % os.getcwd())
51+
print(subprocess.check_output(["ls -l"], shell=True).decode('us-ascii'))
52+
53+
# Remove native app KVS if that was used
54+
kvs_match = re.search(r"--KVS (?P<kvs_path>[^ ]+)", app_args)
55+
if kvs_match:
56+
kvs_path_to_remove = kvs_match.group("kvs_path")
57+
retcode = subprocess.call("rm -f %s" % kvs_path_to_remove, shell=True)
58+
print("Trying to remove KVS path %s" % kvs_path_to_remove)
59+
if retcode != 0:
60+
raise Exception("Failed to remove %s for factory reset." % kvs_path_to_remove)
61+
62+
coloredlogs.install(level='INFO')
63+
64+
log_queue = queue.Queue()
65+
log_cooking_threads = []
66+
67+
if tool_path:
68+
if not os.path.exists(tool_path):
69+
if tool_path is None:
70+
raise FileNotFoundError(f"{tool_path} not found")
71+
72+
app_process = None
73+
if app:
74+
if not os.path.exists(app):
75+
if app is None:
76+
raise FileNotFoundError(f"{app} not found")
77+
app_args = [app] + shlex.split(app_args)
78+
logging.info(f"Execute: {app_args}")
79+
app_process = subprocess.Popen(
80+
app_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0)
81+
DumpProgramOutputToQueue(
82+
log_cooking_threads, Fore.GREEN + "APP " + Style.RESET_ALL, app_process, log_queue)
83+
84+
command = ['java', '-Djava.library.path=' + tool_path + '/lib/jni', '-jar', tool_path + '/bin/java-matter-controller']
85+
86+
if tool_cluster == 'pairing':
87+
logging.info("Testing pairing cluster")
88+
89+
test = CommissioningTest(log_cooking_threads, log_queue, command, tool_args)
90+
controller_exit_code = test.RunTest()
91+
92+
if controller_exit_code != 0:
93+
logging.error("Test script exited with error %r" % test_script_exit_code)
94+
95+
app_exit_code = 0
96+
if app_process:
97+
logging.warning("Stopping app with SIGINT")
98+
app_process.send_signal(signal.SIGINT.value)
99+
app_exit_code = app_process.wait()
100+
101+
# There are some logs not cooked, so we wait until we have processed all logs.
102+
# This procedure should be very fast since the related processes are finished.
103+
for thread in log_cooking_threads:
104+
thread.join()
105+
106+
if controller_exit_code != 0:
107+
sys.exit(controller_exit_code)
108+
else:
109+
# We expect both app and controller should exit with 0
110+
sys.exit(app_exit_code)
111+
112+
113+
if __name__ == '__main__':
114+
main()

0 commit comments

Comments
 (0)