Skip to content

Commit

Permalink
Fixes and WIP harness for CHIP tests
Browse files Browse the repository at this point in the history
The CHIP test harness is not yet complete but it's getting pretty close.  Will continue development as needed as we
migrate away from the "IntegrationTest" approach and rely more on CHIP tests.
  • Loading branch information
lauckhart committed Sep 17, 2024
1 parent 26ef934 commit 06de422
Show file tree
Hide file tree
Showing 49 changed files with 904 additions and 435 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
queries: +security-and-quality
config: |
paths-ignore:
- packages/matter.js/src/node/ServerNode.ts
- packages/node/src/node/ServerNode.ts
- '**/test/**/*Test.ts'
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
Expand Down
37 changes: 32 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,42 @@ The main work (all changes without a GitHub username in brackets in the below li

- IMPORTANT: As of 0.10.0 the @project-chip/matter.js module has grown quite large. This release includes major refactoring that moves functional areas into independent NPM packages under the "@matter.js" org. We have added exports to maintain backwards compatibility but these are not exhaustive. In some cases you may need to update imports to reference new code locations.

- matter.js-general:
- General functionality that is not Matter specific previously resided in @project-chip/matter.js. It now lives in @matter.js/general
- Cross-module changes
- Matter.js now uses aliases via `package.json` "imports" field. This is an internal change that simplifies imports but should not affect consumers
- Previously we used a mix of snake-case and CamelCase for sub-package exports. We have now standardized on snake case. Compatibility packages (see below) continue to support the original module names

- @matter.js/general:
- General functionality that is not Matter specific previously resided in `@project-chip/matter.js`. It now lives in `@matter.js/general`
- BREAKING: The "ByteArray" type is removed, replaced with native-JS Uint8Array and a small collection of utility functions in the "Bytes" namespace
- The Matter object model previously exported as @project-chip/matter.js/model now resides in @matter.js/model
- Feature: The default "Time" implementation is now fully functional across all standard JS runtimes

- @matter.js/model:
- The Matter object model previously exported as `@project-chip/matter.js/model` now resides in `@matter.js/model`
- Individual elements exported by name are now models (fully funcitonal classes) rather than elements (raw JSON data). This should be backwards compatible but makes them more useful operationally

- @matter.js/types:
- Various definitions previously defined in `@project-chip/matter.js` now reside in `@matter.js/types`. This includes most TLV structures, cluster definitions, and various support types
- Clusters are not exported in `@project-chip/matter.js`. You can import via `@project-chip/types/clusters` or individually (e.g. `@project-chip/types/clusters/window-covering`)

- @matter.js/protocol:
- Low-level Matter logic previously defined in `@project-chip/matter.js` now resides in `@matter.js/protocol`. This includes network communication, fabric management and cluster invocation, read/write, events, etc.
- BREAKING: Various types that were previously specialized with template parameters are no longer generic. This should be largely transparent to API consumers. Compatibility exports still support the generic parameters in some, but not all, cases.

- matter.js-nodejs:
- Node specialization is moved to matter.js-nodejs. matter-node.js remains as a compatibility import.
- @matter.js/node:
- The high-level APIs previously defined in `@project-chip/matter.js` now reside in `@matter.js/node`. The Node API includes node management, behavior definitions and endpoint definitions
- We export behaviors under `@matter.js/node/behaviors` or individually (e.g. `@matter.js/node/behaviors/on-off`)
- We export device type definitions for system endpoints and devices under `@matter.js/node/endpoints` and `@matter.js/node/devices` respectively. You may also import these via inddex or individually

- @matter.js/nodejs:
- Node.js specialization is moved here. `@project-chip/matter-node.js` remains as a compatibility import.
- BREAKING: The previously deprecated re-exports in matter-node.js from matter.js are removed.

- @matter.js/nodejs-ble
- The BLE specialization for Node.js is moved here. `@project-chip/matter-node-ble.js` remains as a compatibility import.

- @matter.js/main:
- This package is a new "one-and-done" dependency for applications. It automatically loads platform specialization and reexports pacakages above as appropriate

## 0.10.1 (2024-09-08)

- Matter-Core functionality:
Expand Down
3 changes: 2 additions & 1 deletion chip-testing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"scripts": {
"clean": "matter-build clean",
"build": "matter-build",
"build-clean": "matter-build --clean"
"build-clean": "matter-build --clean",
"wip-test": "matter-test esm"
},
"dependencies": {
"@matter.js/main": "*",
Expand Down
28 changes: 14 additions & 14 deletions chip-testing/src/AllClustersTestInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ import { TestHepaFilterMonitoringServer } from "./cluster/TestHEPAFilterMonitori
import { TestIdentifyServer } from "./cluster/TestIdentifyServer.js";
import { TestLevelControlServer } from "./cluster/TestLevelControlServer.js";
import { TestWindowCoveringServer } from "./cluster/TestWindowCoveringServer.js";
import { TestInstance } from "./GenericTestApp.js";
import { log, TestInstance } from "./GenericTestApp.js";
import { NamedPipeCommandHandler } from "./NamedPipeCommandHandler.js";

export class AllClustersTestInstance implements TestInstance {
Expand Down Expand Up @@ -99,7 +99,7 @@ export class AllClustersTestInstance implements TestInstance {
);
await this.#namedPipeHandler.listen();
} catch (error) {
console.log("Error creating named pipe:", error);
log.error("Error creating named pipe:", error);
}
}

Expand All @@ -110,11 +110,11 @@ export class AllClustersTestInstance implements TestInstance {
await this.#setupNamedPipe();
} catch (error) {
// Catch and log error, else the test framework hides issues here
console.log(error);
console.log((error as Error).stack);
log.error(error);
log.error((error as Error).stack);
throw error;
}
console.log(`======> ${this.appName}: Setup done`);
log.directive(`======> ${this.appName}: Setup done`);
}

/** Start the test instance MatterServer with the included device. */
Expand All @@ -130,18 +130,18 @@ export class AllClustersTestInstance implements TestInstance {
await this.serverNode.start();
const { qrPairingCode } = this.serverNode.state.commissioning.pairingCodes;
// Magic logging chip testing waits for
console.log(`SetupQRCode: [${qrPairingCode}]`);
console.log();
log.directive(`SetupQRCode: [${qrPairingCode}]`);
log.directive();
// Magic logging chip testing waits for
console.log("mDNS service published:");
console.log();
log.directive("mDNS service published:");
log.directive();

console.log(`======> ${this.appName}: Instance started`);
log.directive(`======> ${this.appName}: Instance started`);
} catch (error) {
// Catch and log error, else the test framework hides issues here
console.log(error);
log.error(error);
}
console.log("=====>>> STARTED");
log.directive("=====>>> STARTED");
}

/** Stop the test instance MatterServer and the device. */
Expand All @@ -154,9 +154,9 @@ export class AllClustersTestInstance implements TestInstance {
try {
await this.#namedPipeHandler?.close();
} catch (error) {
console.log("Error closing named pipe:", error);
log.error("Error closing named pipe:", error);
}
console.log(`======> ${this.appName}: Instance stopped`);
log.directive(`======> ${this.appName}: Instance stopped`);
}

async setupServer(): Promise<ServerNode> {
Expand Down
24 changes: 12 additions & 12 deletions chip-testing/src/BridgeTestInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { AdministratorCommissioning, BasicInformation, NetworkCommissioning } fr
import { DimmableLightDevice } from "@matter.js/main/devices/dimmable-light";
import { AggregatorEndpoint } from "@matter.js/main/endpoints/aggregator";
import { DeviceTypeId, VendorId } from "@matter.js/main/types";
import { TestInstance } from "./GenericTestApp.js";
import { log, TestInstance } from "./GenericTestApp.js";

export class BridgeTestInstance implements TestInstance {
serverNode: ServerNode | undefined;
Expand All @@ -40,11 +40,11 @@ export class BridgeTestInstance implements TestInstance {
this.serverNode = await this.setupServer();
} catch (error) {
// Catch and log error, else the test framework hides issues here
console.log(error);
console.log((error as Error).stack);
log.error(error);
log.error((error as Error).stack);
throw error;
}
console.log(`======> ${this.appName}: Setup done`);
log.directive(`======> ${this.appName}: Setup done`);
}

/** Start the test instance MatterServer with the included device. */
Expand All @@ -60,18 +60,18 @@ export class BridgeTestInstance implements TestInstance {
await this.serverNode.start();
const { qrPairingCode } = this.serverNode.state.commissioning.pairingCodes;
// Magic logging chip testing waits for
console.log(`SetupQRCode: [${qrPairingCode}]`);
console.log();
log.directive(`SetupQRCode: [${qrPairingCode}]`);
log.directive();
// Magic logging chip testing waits for
console.log("mDNS service published:");
console.log();
log.directive("mDNS service published:");
log.directive();

console.log(`======> ${this.appName}: Instance started`);
log.directive(`======> ${this.appName}: Instance started`);
} catch (error) {
// Catch and log error, else the test framework hides issues here
console.log(error);
log.error(error);
}
console.log("=====>>> STARTED");
log.directive("=====>>> STARTED");
}

/** Stop the test instance MatterServer and the device. */
Expand All @@ -81,7 +81,7 @@ export class BridgeTestInstance implements TestInstance {
//this.serverNode.cancel();
//await this.serverNode.lifecycle.act;
this.serverNode = undefined;
console.log(`======> ${this.appName}: Instance stopped`);
log.directive(`======> ${this.appName}: Instance stopped`);
}

async setupServer(): Promise<ServerNode> {
Expand Down
46 changes: 40 additions & 6 deletions chip-testing/src/GenericTestApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,67 @@
*/

import { ClassExtends, Environment } from "@matter.js/main";
import { ValidationError } from "@matter.js/main/types";
import { StorageBackendAsyncJsonFile } from "./storage/StorageBackendAsyncJsonFile.js";
import { StorageBackendSyncJsonFile } from "./storage/StorageBackendSyncJsonFile.js";

// TODO - the get*Parameter calls are deprecated along with matter-node.js so copied here temporarily. Replace with
// yargs ?
const commandArguments = process.argv.slice(2);

export function getParameter(name: string) {
let markerIndex = commandArguments.indexOf(`-${name}`);
if (markerIndex === -1) markerIndex = commandArguments.indexOf(`--${name}`);
if (markerIndex === -1 || markerIndex + 1 === commandArguments.length) return undefined;
return commandArguments[markerIndex + 1];
}

export function hasParameter(name: string) {
let markerIncluded = commandArguments.includes(`-${name}`);
if (!markerIncluded) markerIncluded = commandArguments.includes(`--${name}`);
return markerIncluded;
}

export function getIntParameter(name: string) {
const value = getParameter(name);
if (value === undefined) return undefined;
const intValue = parseInt(value, 10);
if (isNaN(intValue)) throw new ValidationError(`Invalid value for parameter ${name}: ${value} is not a number`);
return intValue;
}

export interface TestInstance {
setup: () => Promise<void>;
start: () => Promise<void>;
stop: () => Promise<void>;
}

export namespace log {
export function directive(...args: unknown[]) {
console.log(...args);
}

export function error(...args: unknown[]) {
console.log(args);
}
}

export async function startTestApp(
appName: string,
testInstanceClass: ClassExtends<TestInstance>,
storageType: typeof StorageBackendSyncJsonFile | typeof StorageBackendAsyncJsonFile = StorageBackendSyncJsonFile,
) {
const vars = Environment.default.vars;

const storageName = `/tmp/chip_${vars.string("KVS") ?? "kvs"}`;
const storageName = `/tmp/chip_${getParameter("KVS") ?? "kvs"}`;

const storage = new storageType(storageName);
if (vars.boolean("factoryreset")) {
if (hasParameter("factoryreset")) {
await storage.clear();
}

const testInstance = new testInstanceClass(storage, {
appName,
discriminator: vars.number("discriminator"),
passcode: vars.string("passcode"),
discriminator: getIntParameter("discriminator"),
passcode: getIntParameter("passcode"),
});

await testInstance.setup();
Expand Down
24 changes: 12 additions & 12 deletions chip-testing/src/TvTestInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { WakeOnLanServer } from "@matter.js/main/behaviors/wake-on-lan";
import { AdministratorCommissioning, ApplicationBasic, BasicInformation } from "@matter.js/main/clusters";
import { DimmableLightDevice } from "@matter.js/main/devices/dimmable-light";
import { DeviceTypeId, EndpointNumber, VendorId } from "@matter.js/main/types";
import { TestInstance } from "./GenericTestApp.js";
import { log, TestInstance } from "./GenericTestApp.js";
import { TestLowPowerServer } from "./cluster/TestLowPowerServer.js";

export class TvTestInstance implements TestInstance {
Expand Down Expand Up @@ -40,11 +40,11 @@ export class TvTestInstance implements TestInstance {
this.serverNode = await this.setupServer();
} catch (error) {
// Catch and log error, else the test framework hides issues here
console.log(error);
console.log((error as Error).stack);
log.error(error);
log.error((error as Error).stack);
throw error;
}
console.log(`======> ${this.appName}: Setup done`);
log.directive(`======> ${this.appName}: Setup done`);
}

/** Start the test instance MatterServer with the included device. */
Expand All @@ -60,18 +60,18 @@ export class TvTestInstance implements TestInstance {
await this.serverNode.start();
const { qrPairingCode } = this.serverNode.state.commissioning.pairingCodes;
// Magic logging chip testing waits for
console.log(`SetupQRCode: [${qrPairingCode}]`);
console.log();
log.directive(`SetupQRCode: [${qrPairingCode}]`);
log.directive();
// Magic logging chip testing waits for
console.log("mDNS service published:");
console.log();
log.directive("mDNS service published:");
log.directive();

console.log(`======> ${this.appName}: Instance started`);
log.directive(`======> ${this.appName}: Instance started`);
} catch (error) {
// Catch and log error, else the test framework hides issues here
console.log(error);
log.error(error);
}
console.log("=====>>> STARTED");
log.directive("=====>>> STARTED");
}

/** Stop the test instance MatterServer and the device. */
Expand All @@ -81,7 +81,7 @@ export class TvTestInstance implements TestInstance {
//this.serverNode.cancel();
//await this.serverNode.lifecycle.act;
this.serverNode = undefined;
console.log(`======> ${this.appName}: Instance stopped`);
log.directive(`======> ${this.appName}: Instance stopped`);
}

async setupServer(): Promise<ServerNode> {
Expand Down
12 changes: 12 additions & 0 deletions chip-testing/test/AccessCheckerTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @license
* Copyright 2022-2024 Matter.js Authors
* SPDX-License-Identifier: Apache-2.0
*/

import { AllClustersTestInstance } from "../src/AllClustersTestInstance.js";
import { App } from "./support.js";

describe("AccessChecker", () => {
Chip.python(App(AllClustersTestInstance), "TC_AccessChecker");
});
15 changes: 15 additions & 0 deletions chip-testing/test/AceTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* @license
* Copyright 2022-2024 Matter.js Authors
* SPDX-License-Identifier: Apache-2.0
*/

import { AllClustersTestInstance } from "../src/AllClustersTestInstance.js";
import { App } from "./support.js";

describe("ACE", () => {
Chip.python(App(AllClustersTestInstance), "TC_ACE_1_2");
Chip.python(App(AllClustersTestInstance), "TC_ACE_1_3");
Chip.python(App(AllClustersTestInstance), "TC_ACE_1_4");
Chip.python(App(AllClustersTestInstance), "TC_ACE_1_5");
});
12 changes: 12 additions & 0 deletions chip-testing/test/AclTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @license
* Copyright 2022-2024 Matter.js Authors
* SPDX-License-Identifier: Apache-2.0
*/

import { AllClustersTestInstance } from "../src/AllClustersTestInstance.js";
import { App } from "./support.js";

describe("ACL", () => {
Chip.python(App(AllClustersTestInstance), "TC_ACL_2_2");
});
12 changes: 12 additions & 0 deletions chip-testing/test/CadminTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @license
* Copyright 2022-2024 Matter.js Authors
* SPDX-License-Identifier: Apache-2.0
*/

import { AllClustersTestInstance } from "../src/AllClustersTestInstance.js";
import { App } from "./support.js";

describe("CADMIN", () => {
Chip.python(App(AllClustersTestInstance), "TC_CADMIN_1_9");
});
12 changes: 12 additions & 0 deletions chip-testing/test/CgenTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @license
* Copyright 2022-2024 Matter.js Authors
* SPDX-License-Identifier: Apache-2.0
*/

import { AllClustersTestInstance } from "../src/AllClustersTestInstance.js";
import { App } from "./support.js";

describe("CGEN", () => {
Chip.python(App(AllClustersTestInstance), "TC_CGEN_2_4");
});
Loading

0 comments on commit 06de422

Please sign in to comment.