Skip to content

Commit

Permalink
Merge branch 'main' into feature/add-bash-completion
Browse files Browse the repository at this point in the history
  • Loading branch information
rjaegers committed Sep 27, 2024
2 parents 79ec088 + cc9380c commit c792a40
Show file tree
Hide file tree
Showing 24 changed files with 1,158 additions and 91 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/cpp/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ FROM ubuntu:24.04@sha256:dfc10878be8d8fc9c61cbff33166cb1d1fe44391539243703c72766
ARG BATS_VERSION=1.11.0
ARG CCACHE_VERSION=4.10.1
ARG CLANG_VERSION=17
ARG DOCKER_VERSION=27.0.3
ARG DOCKER_VERSION=27.3.1
ARG MULL_VERSION=0.22.0
ARG INCLUDE_WHAT_YOU_USE_VERSION=0.21
ARG XWIN_VERSION=0.6.5
Expand Down
12 changes: 8 additions & 4 deletions .devcontainer/cpp/devcontainer-metadata-vscode.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,24 @@
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]"
"[email protected]"
],
"settings": {
"C_Cpp.intelliSenseEngine": "disabled",
"C_Cpp.formatting": "clangFormat",
"clangd.arguments": [
"--query-driver=/opt/**/arm-none-eabi-*",
"--compile-commands-dir=${userHome}/.amp"
],
"cmake.copyCompileCommands": "${userHome}/.amp/compile_commands.json",
"cortex-debug.gdbPath": "gdb-multiarch",
"cortex-debug.objdumpPath": "arm-none-eabi-objdump",
"sonarlint.pathToCompileCommands": "/root/.amp/compile_commands.json"
"sonarlint.pathToCompileCommands": "/root/.amp/compile_commands.json",
"[c]": {
"editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd"
},
"[cpp]": {
"editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd"
}
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions .devcontainer/cpp/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,16 @@
"customizations": {
"vscode": {
"settings": {
"cucumberautocomplete.steps": [".devcontainer/cpp/e2e/features/steps/*.steps.ts"],
"cucumberautocomplete.strictGherkinCompletion": false,
"cucumberautocomplete.strictGherkinValidation": false,
"cucumberautocomplete.smartSnippets": true,
"cucumberautocomplete.onTypeFormat": true,
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true
},
"extensions": [
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
Expand Down
18 changes: 18 additions & 0 deletions .devcontainer/cpp/e2e/features/compilation.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Feature: Compile source code into working software

As a developer
In order to generate working software
Source code needs to be compiled successfully

Scenario: Compile valid source code into working software targeting the host architecture

Compiling valid source code into working software, able to run on the host architecture,
can be necessary in several scenarios; for example when:

- the host is the deployment target
- running tests on the host
- building plug-ins, extensions, code generators, or other additional tools that need to run on the host

Given the default build configuration is selected
When the configuration "host" is built
Then the output should contain "Build finished with exit code 0"
37 changes: 37 additions & 0 deletions .devcontainer/cpp/e2e/features/pages/authentication.pom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { type Page } from '@playwright/test';
import * as OTPAuth from 'otpauth';
import { STORAGE_STATE } from '../../playwright.config';

export class AuthenticationPage {
readonly page: Page;

constructor(page: Page) {
this.page = page;
}

async authenticate() {
await this.page.goto('https://github.com/login');
await this.page.getByLabel('Username or email address').fill(process.env.GITHUB_USER!);
await this.page.getByLabel('Password').fill(process.env.GITHUB_PASSWORD!);
await this.page.getByRole('button', { name: 'Sign in', exact: true }).click();

let totp = new OTPAuth.TOTP({
issuer: 'GitHub',
label: 'GitHub',
algorithm: 'SHA1',
digits: 6,
period: 30,
secret: process.env.GITHUB_TOTP_SECRET!
});

let code = totp.generate();
await this.page.getByPlaceholder('XXXXXX').fill(code);

// Wait until the page receives the cookies.
//
// Sometimes login flow sets cookies in the process of several redirects.
// Wait for the final URL to ensure that the cookies are actually set.
await this.page.waitForURL('https://github.com/');
await this.page.context().storageState({ path: STORAGE_STATE });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,25 @@ export class CodespacePage {
await expect(this.page.getByRole('button', { name: 'Activating Extensions...' })).toBeHidden();
}

/**
* Executes the given commands in the command palette.
*
* This method waits for `prompt` to appear and then types `command` and presses Enter.
* When no prompt is given the default prompt is used.
*
* @param commands - The commands to execute in the command palette. It can be a single command or an array of commands.
*/
async executeFromCommandPalette(commands: CommandAndPrompt | CommandAndPrompt[]) {
await this.page.keyboard.press('Control+Shift+P');

for (const command of Array.isArray(commands) ? commands : [commands]) {
let prompt = this.page.getByPlaceholder(command.prompt || 'Type the name of a command to run');

await prompt.fill(`> ${command.command}`);
await prompt.press('Enter');
}
}

/**
* Executes the given commands in the terminal.
*
Expand All @@ -59,34 +78,15 @@ export class CodespacePage {
* @param commands - The commands to execute in the terminal. It can be a single command or an array of commands.
*/
async executeInTerminal(commands: string | string[]) {
await this.page.keyboard.press('Control+Shift+`');
await expect(this.page.locator('.terminal-wrapper.active')).toBeVisible();
await this.executeFromCommandPalette({ command: 'Terminal: Focus on Terminal View' });
await expect(this.page.locator('.terminal-widget-container')).toBeVisible();

for (const command of Array.isArray(commands) ? [...commands + 'exit'] : [commands, 'exit']) {
for (const command of Array.isArray(commands) ? commands : [commands]) {
await this.terminal.pressSequentially(command);
await this.terminal.press('Enter');
}
}

/**
* Executes the given commands in the command palette.
*
* This method waits for `prompt` to appear and then types `command` and presses Enter.
* When no prompt is given the default prompt is used.
*
* @param commands - The commands to execute in the command palette. It can be a single command or an array of commands.
*/
async executeFromCommandPalette(commands: CommandAndPrompt | CommandAndPrompt[]) {
await this.page.keyboard.press('Control+Shift+P');

for (const command of Array.isArray(commands) ? commands : [commands]) {
let prompt = this.page.getByPlaceholder(command.prompt || 'Type the name of a command to run');

await prompt.pressSequentially(command.command);
await prompt.press('Enter');
}
}

/**
* Opens the tab with the given name.
*
Expand All @@ -100,4 +100,30 @@ export class CodespacePage {
await this.page.getByRole('treeitem', { name: name }).locator('a').click();
await expect(this.page.locator('[id="workbench.parts.editor"]')).toContainText(name);
}

async openCppFileInEditor(name: string) {
await this.openFileInEditor(name);
await expect(this.page.locator('[id="llvm-vs-code-extensions.vscode-clangd"]')).toContainText('clangd: idle', { timeout: 1 * 60 * 1000 });
}

async formatDocument() {
await this.executeFromCommandPalette({ command: 'Format Document' });
}

async saveDocument() {
await this.page.keyboard.press('Control+S');
}

async buildSelectedTarget() {
await this.page.getByRole('button', { name: 'Build the selected target' }).click();
}

async expectEditorContent(expected: RegExp) {
await expect(this.page.getByRole('code')).toContainText(expected);
}

async expectFileContentsToMatch(actual: string, expected: string) {
await this.executeInTerminal(`diff -s ${actual} ${expected}`);
await expect(this.page.locator('#terminal')).toContainText(`Files ${actual} and ${expected} are identical`);
}
}
12 changes: 12 additions & 0 deletions .devcontainer/cpp/e2e/features/static-dynamic-analysis.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Feature: Analyze source code using static and dynamic analysis

As a software craftsman
To maintain consistent, high-quality and bug-free code
Source code needs to be statically and dynamically analyzed

Scenario: Format source code according to a formatting style

Given the file "unformatted.cpp" is opened in the editor
When the active document is formatted
And the active document is saved
Then the contents of "unformatted.cpp" should match the contents of "formatted.cpp"
44 changes: 44 additions & 0 deletions .devcontainer/cpp/e2e/features/steps/codespace.steps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { expect } from "@playwright/test";
import { Given, When, Then } from "./fixtures";
import * as path from 'path';

Given("the default build configuration is selected", async () => {
// No-op
});

Given("the file {string} is opened in the editor", async ({ codespacePage }, file: string) => {
const fileExtension = path.extname(file).slice(1);

switch (fileExtension) {
case 'cpp':
await codespacePage.openCppFileInEditor(file);
break;
default:
await codespacePage.openFileInEditor(file);
}
});

When("the configuration {string} is built", async ({ codespacePage }, configuration: string) => {
await codespacePage.page.getByRole('button', { name: 'Build the selected target' }).click();
await codespacePage.page.getByLabel(configuration).locator('a').click();
});

When("the active document is formatted", async ({ codespacePage }) => {
await codespacePage.formatDocument();
});

When("the active document is saved", async ({ codespacePage }) => {
await codespacePage.saveDocument();
});

Then("the output should contain {string}", async ({ codespacePage }, expectedOutput: string) => {
await expect(codespacePage.outputPanel).toContainText(expectedOutput, { timeout: 5 * 60 * 1000 });
});

Then("the editor should contain {string}", async ({ codespacePage }, expectedContent: string) => {
await codespacePage.expectEditorContent(new RegExp(expectedContent));
});

Then("the contents of {string} should match the contents of {string}", async ({ codespacePage }, actual: string, expected: string) => {
await codespacePage.expectFileContentsToMatch(actual, expected);
});
24 changes: 24 additions & 0 deletions .devcontainer/cpp/e2e/features/steps/fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { AuthenticationPage } from '../pages/authentication.pom';
import { CodespacePage } from '../pages/codespace.pom';
import { test as base, createBdd } from 'playwright-bdd';

export const test = base.extend<{ codespacePage: CodespacePage }, { authenticationPage: AuthenticationPage }>({
authenticationPage: [async ({ browser }, use) => {
let authenticationPage = new AuthenticationPage(await browser.newPage());
await authenticationPage.authenticate();

await use(authenticationPage);
}, { scope: 'worker', auto: true }
],
codespacePage: async ({ page }, use) => {
const codespacePage = new CodespacePage(page);
await codespacePage.goto();
await codespacePage.areExtensionsActive(['Testing', 'SonarLint', 'CMake', 'Live Share', 'GitHub Pull Requests']);

await use(codespacePage);

await codespacePage.executeInTerminal('git clean -fdx');
},
});

export const { Given, When, Then } = createBdd(test);
14 changes: 9 additions & 5 deletions .devcontainer/cpp/e2e/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { defineConfig, devices } from '@playwright/test';
import { defineBddConfig } from "playwright-bdd";
import path from 'path';

require('dotenv').config();
require('dotenv').config({ path: path.join(__dirname, '..', '..', '..', '.env') });

export const STORAGE_STATE = path.join(__dirname, 'playwright/.auth/user.json');

const testDir = defineBddConfig({
features: "features/*.feature",
steps: ["features/steps/*.ts"],
});

export default defineConfig({
testDir: './tests',
testDir,
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
Expand All @@ -16,14 +22,12 @@ export default defineConfig({
trace: 'on-first-retry'
},
projects: [
{ name: 'setup', testMatch: '**/*.setup.ts' },
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: STORAGE_STATE
},
dependencies: ['setup']
}
}
]
});
29 changes: 0 additions & 29 deletions .devcontainer/cpp/e2e/tests/authentication.setup.ts

This file was deleted.

20 changes: 0 additions & 20 deletions .devcontainer/cpp/e2e/tests/smoke.spec.ts

This file was deleted.

2 changes: 0 additions & 2 deletions .devcontainer/cpp/e2e/workspace/CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
},
{
"name": "host",
"displayName": "host",
"description": "Build for host",
"inherits": "defaults"
}
],
Expand Down
7 changes: 7 additions & 0 deletions .devcontainer/cpp/e2e/workspace/formatted.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include <algorithm>
#include <iostream>

int main() {
std::cout << "Hello World!" << std::endl;
return 0;
}
Loading

0 comments on commit c792a40

Please sign in to comment.