Skip to content

Commit bc99877

Browse files
Initial unit test framework (#45)
* Initial unit test framework * Add CodeClimate badges * Enhance test cases --------- Co-authored-by: Jens Reinecke <[email protected]>
1 parent 16297df commit bc99877

File tree

9 files changed

+446
-3
lines changed

9 files changed

+446
-3
lines changed

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
[![Maintainability](https://api.codeclimate.com/v1/badges/0f12a7e73736f8bbfb9d/maintainability)](https://codeclimate.com/github/Open-CMSIS-Pack/vscode-cmsis-debugger/maintainability)
2+
[![Test Coverage](https://api.codeclimate.com/v1/badges/0f12a7e73736f8bbfb9d/test_coverage)](https://codeclimate.com/github/Open-CMSIS-Pack/vscode-cmsis-debugger/test_coverage)
3+
14
# Arm CMSIS Debugger Extension for Visual Studio Code (In Progress)
25

3-
The Arm CMSIS Debugger extension for Visual Studio Code is an extension pack demonstrating how to combine technologies from various open source projects to create a comprehensive debug platform for Arm based IoT solutions.
6+
The Arm CMSIS Debugger extension for Visual Studio Code is an extension pack demonstrating how to combine technologies from various open source projects to create a comprehensive debug platform for Arm based IoT solutions.
47

58
Related open source projects are
9+
610
- [Open-CMSIS-Pack](https://www.open-cmsis-pack.org/) of which this extension is part of.
711
- [Eclipse CDT Cloud](https://eclipse.dev/cdt-cloud/), an open-source project that hosts a number of components and best practices for building customizable web-based C/C++ tools.
812
- [pyOCD](https://pyocd.io/), a Python based tool and API for debugging, programming, and exploring Arm Cortex microcontrollers.
@@ -15,6 +19,7 @@ The Arm CMSIS Debugger extension is actually an [extension pack](https://code.vi
1519
## Included Extensions
1620

1721
The following extensions are included in this extension pack:
22+
1823
- [Arm Tools Environment Manager](https://marketplace.visualstudio.com/items?itemName=Arm.environment-manager), an extension that allows to download, install, and manage software development tools using [Microsoft vcpkg](https://vcpkg.io/en/index.html) artifacts.
1924
- [CDT GDB Debug Adapter Extension](https://marketplace.visualstudio.com/items?itemName=eclipse-cdt.cdt-gdb-vscode), an Eclipse CDT Cloud extension that supports debugging using gdb and any other debuggers that supports the MI protocol.
2025
- [Memory Inspector](https://marketplace.visualstudio.com/items?itemName=eclipse-cdt.memory-inspector), an Eclipse CDT Cloud extension that provides a powerful and configurable memory viewer that works with debug adapters.
@@ -23,10 +28,11 @@ The following extensions are included in this extension pack:
2328
## pyOCD Debug Setup
2429

2530
- Install `GCC compiler for ARM CPUs` with the `Arm Tools Environment Manager` to get access to a GDB (`arm-none-eabi-gdb`).
26-
- **Temporary** - should become obsolete with full `*.cbuild-run.yml` support in pyOCD:<br>
31+
- **Temporary** - should become obsolete with full `*.cbuild-run.yml` support in pyOCD:<br>
2732
Make sure to set up your CMSIS Pack installation root folder by one of the following methods:
2833
- Set your system environment variable `CMSIS_PACK_ROOT`.
2934
- Add the following to your debug launch configuration
35+
3036
```
3137
"environment": {
3238
"CMSIS_PACK_ROOT": "</path/to/your/pack/cache>"
@@ -41,6 +47,7 @@ The following extensions are included in this extension pack:
4147
## Additional Extension Functionality
4248
4349
This extension contributes additional functionality to more seamlessly integrate the included extensions:
50+
4451
- The pseudo debugger types `cmsis-debug-pyocd` and `cmsis-debug-jlink`. These types allow a more seamless integration into the VS Code IDE. However, these are not full debug adapters but generate debug configurations of type `gdbtarget` which comes with the [CDT GDB Debug Adapter Extension](https://marketplace.visualstudio.com/items?itemName=eclipse-cdt.cdt-gdb-vscode).
4552
- A [debug configuration provider](https://code.visualstudio.com/api/references/vscode-api#DebugConfigurationProvider) for the type `gdbtarget` which comes with the [CDT GDB Debug Adapter Extension](https://marketplace.visualstudio.com/items?itemName=eclipse-cdt.cdt-gdb-vscode). This provider automatically fills in default values for known remote GDB servers when launching a debug session.
4653
- CMSIS specific launch configuration items for the `*` debugger type, i.e. visible for all debugger types. It depends on the actually used debug adapter type if this information is known and utilized.
@@ -54,6 +61,7 @@ This section describes the contributed pseudo debugger types and their support t
5461
The `cmsis-debug-pyocd` debugger type allows to add default debug configurations to the workspace's `launch.json` file to debug via GDB and pyOCD. The actually used debugger type is `gdbtarget`.
5562
5663
In addition this extension contributes a debug configuration resolver which automatically fills the following gaps during debug launch:
64+
5765
- If option `target`.`server` is set to `pyocd`, then it expands this option to the absolute path of the built-in pyOCD distribution.
5866
- Adds/extends the `target`.`serverParameters` list of `pyocd` command line arguments:
5967
- Prepends `gdbserver` if not present.
@@ -69,12 +77,15 @@ The `cmsis-debug-jlink` debugger type allows to add default debug configurations
6977
**Note**: The generated default debug configuration uses `JLinkGDBServer` as `target`.`server` setting. The executable with this name has slightly differing behavior depending on your host platform. It launches a GUI-less server on Linux and MacOS. Whereas a GDB server with GUI is launched on Windows. Please change the value to `JLinkGDBServerCL` to suppress the GUI on Windows.
7078
7179
In addition this extension contributes a debug configuration resolver which automatically fills the following gaps during debug launch:
80+
7281
- Adds/extends the `target`.`serverParameters` list of `JLinkGDBServer`/`JLinkGDBServerCL` command line arguments:
7382
- Appends `--port` and the corresponding `port` value if `target`.`port` is set.
7483
7584
## Known Limitations
85+
7686
- Requires ELF files built with GCC and DWARF5 debug information to operate seamlessly.
7787
- The shipped pyOCD version accepts the new command line option `--cbuild-run`. But only extracts device and DFP names.
7888
7989
## Trademarks
90+
8091
Visual Studio is a trademark of the Microsoft group of companies.

__mocks__/vscode.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,32 @@
1414
* limitations under the License.
1515
*/
1616

17+
require('domain');
1718
const { URI } = require('vscode-uri');
1819

1920
module.exports = {
21+
EventEmitter: jest.fn(() => {
22+
const callbacks = [];
23+
return {
24+
dispose: jest.fn(),
25+
event: (callback, thisArg) => {
26+
callbacks.push(thisArg ? callback.bind(thisArg) : callback);
27+
return { dispose: jest.fn() };
28+
},
29+
fire: event => callbacks.forEach(callback => callback(event))
30+
};
31+
}),
2032
Uri: URI,
33+
window: {
34+
createOutputChannel: jest.fn(() => ({
35+
appendLine: jest.fn(),
36+
trace: jest.fn(),
37+
debug: jest.fn(),
38+
info: jest.fn(),
39+
warn: jest.fn(),
40+
error: jest.fn(),
41+
})),
42+
},
2143
workspace: {
2244
getConfiguration: jest.fn(() => ({
2345
get: jest.fn(),
@@ -28,5 +50,8 @@ module.exports = {
2850
},
2951
commands: {
3052
executeCommand: jest.fn(),
31-
}
53+
},
54+
debug: {
55+
registerDebugConfigurationProvider: jest.fn(),
56+
},
3257
};
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* Copyright 2025 Arm Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { makeFactory, StubEvents } from './test-data-factory';
18+
import { Event, EventEmitter } from 'vscode';
19+
20+
describe('makeFactory', () => {
21+
22+
it('returns default values when used without options', () => {
23+
type Type = {
24+
value: number;
25+
text: string;
26+
}
27+
28+
const expected : Type = {
29+
value: 42,
30+
text: 'the answer'
31+
};
32+
33+
const factory = makeFactory<Type>({
34+
value: () => expected.value,
35+
text: () => expected.text,
36+
});
37+
38+
const value = factory();
39+
40+
expect(value).toEqual(expected);
41+
});
42+
43+
it('returns explicit values passed by options', () => {
44+
type Type = {
45+
value: number;
46+
text: string;
47+
}
48+
49+
const expected : Type = {
50+
value: 42,
51+
text: 'the answer'
52+
};
53+
54+
const factory = makeFactory<Type>({
55+
value: () => 43,
56+
text: () => expected.text,
57+
});
58+
59+
const value = factory({ value: expected.value });
60+
61+
expect(value).toEqual(expected);
62+
});
63+
64+
it('stubs vscode.Event|s by exposing vscode.EventEmitter', () => {
65+
type Type = {
66+
event: Event<number>,
67+
}
68+
69+
const factory = makeFactory<StubEvents<Type>>({
70+
eventEmitter: () => new EventEmitter(),
71+
event: (r) => jest.fn(r.eventEmitter?.event),
72+
});
73+
74+
const value = factory();
75+
76+
const listener = jest.fn();
77+
value.event(listener);
78+
value.eventEmitter.fire(42);
79+
80+
expect(listener).toHaveBeenCalledTimes(1);
81+
expect(listener).toHaveBeenCalledWith(42);
82+
});
83+
84+
});

src/__test__/test-data-factory.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Copyright 2025 Arm Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { Event, EventEmitter } from 'vscode';
18+
19+
type Mutable<T> = {
20+
-readonly [P in keyof T]: T[P];
21+
}
22+
23+
type Factory<T> = (options?: Partial<T>) => Mutable<T>;
24+
25+
type Initializer<T> = {
26+
[P in keyof T]: (result: Partial<T>) => T[P]
27+
};
28+
29+
export type StubEvents<T, S extends string = 'Emitter'> = {
30+
[P in keyof T as T[P] extends Event<unknown> ? `${string & P}${S}` : never]: T[P] extends Event<infer U> ? EventEmitter<U> : never
31+
} & T;
32+
33+
export function makeFactory<T extends object>(initializer: Initializer<T>): Factory<T> {
34+
const factory = (options?: Partial<T>) => {
35+
const result = { ...options } as Mutable<T>;
36+
for (const key in initializer) {
37+
if (!(key in result)) {
38+
result[key] = initializer[key].call(result, result);
39+
}
40+
}
41+
return result;
42+
};
43+
return factory;
44+
}
45+
46+
export function makeGenerator<T>(factory: Factory<T>) {
47+
return (count: number = 1, options?: Partial<T>): T[] => {
48+
return [...Array(count)].map(_ => factory(options));
49+
};
50+
}

src/__test__/vscode.factory.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Copyright 2025 Arm Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as vscode from 'vscode';
18+
19+
export function extensionContextFactory(): jest.Mocked<vscode.ExtensionContext> {
20+
return {
21+
subscriptions: [],
22+
workspaceState: {
23+
get: jest.fn(),
24+
update: jest.fn(),
25+
} as unknown as vscode.Memento,
26+
globalState: {
27+
get: jest.fn(),
28+
update: jest.fn(),
29+
setKeysForSync: jest.fn(),
30+
} as unknown as vscode.Memento & { setKeysForSync(keys: readonly string[]): void } ,
31+
secrets: {
32+
store: jest.fn(),
33+
get: jest.fn(),
34+
delete: jest.fn(),
35+
} as unknown as vscode.SecretStorage,
36+
extensionUri: vscode.Uri.file('/mock/uri'),
37+
extensionPath: '/mock/path',
38+
environmentVariableCollection: {
39+
persistent: true,
40+
replace: jest.fn(),
41+
append: jest.fn(),
42+
prepend: jest.fn(),
43+
get: jest.fn(),
44+
forEach: jest.fn(),
45+
getScoped: jest.fn(),
46+
} as unknown as vscode.GlobalEnvironmentVariableCollection,
47+
storageUri: vscode.Uri.file('/mock/storageUri'),
48+
globalStorageUri: vscode.Uri.file('/mock/globalStorageUri'),
49+
logUri: vscode.Uri.file('/mock/logUri'),
50+
storagePath: '/mock/storagePath',
51+
globalStoragePath: '/mock/globalStoragePath',
52+
logPath: '/mock/logPath',
53+
asAbsolutePath: jest.fn((relativePath: string) => `/mock/path/${relativePath}`),
54+
extensionMode: 3,
55+
extension: {
56+
id: 'mock.extension',
57+
extensionUri: vscode.Uri.file('/mock/uri'),
58+
extensionPath: '/mock/path',
59+
isActive: true,
60+
packageJSON: {},
61+
activate: jest.fn(),
62+
exports: {},
63+
extensionKind: 2,
64+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
65+
} as unknown as vscode.Extension<any>,
66+
languageModelAccessInformation: {
67+
getLanguageModel: jest.fn(),
68+
} as unknown as vscode.LanguageModelAccessInformation,
69+
};
70+
};
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* Copyright 2025 Arm Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as vscode from 'vscode';
18+
import { makeFactory } from '../__test__/test-data-factory';
19+
import { GDBTargetConfiguration, TargetConfiguration } from './gdbtarget-configuration';
20+
21+
export const debugConfigurationFactory = makeFactory<vscode.DebugConfiguration>({
22+
type: () => 'cppdbg',
23+
name: () => 'Debug',
24+
request: () => 'launch',
25+
});
26+
27+
export const targetConfigurationFactory = makeFactory<TargetConfiguration>({
28+
type: () => undefined,
29+
parameters: () => undefined,
30+
host: () => undefined,
31+
port: () => undefined,
32+
cwd: () => undefined,
33+
environment: () => undefined,
34+
server: () => undefined,
35+
serverParameters: () => undefined,
36+
serverPortRegExp: () => undefined,
37+
serverStartupDelay: () => undefined,
38+
automaticallyKillServer: () => false,
39+
uart: () => undefined,
40+
});
41+
42+
export const gdbTargetConfiguration = makeFactory<GDBTargetConfiguration>({
43+
type: () => 'gdb',
44+
name: () => 'Debug',
45+
request: () => 'launch',
46+
program: () => undefined,
47+
gdb: () => undefined,
48+
cwd: () => undefined,
49+
environment: () => undefined,
50+
gdbAsync: () => undefined,
51+
gdbNonStop: () => undefined,
52+
verbose: () => undefined,
53+
logFile: () => undefined,
54+
openGdbConsole: () => undefined,
55+
initCommands: () => undefined,
56+
preRunCommands: () => undefined,
57+
imageAndSymbols: () => undefined,
58+
target: () => undefined,
59+
cmsis: () => undefined,
60+
});

0 commit comments

Comments
 (0)