diff --git a/CHANGELOG.md b/CHANGELOG.md index 31c05e01..62c028b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added + +(experimental) CppUTest Framework Support + ### Changed - activation event is `onStartupFinished` from now. This mean stat the extension will be activated every timethe vscode is opened. diff --git a/README.md b/README.md index 17e9780a..6a1c47cb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # C++ TestMate -## A **Catch2**, **GoogleTest**, **doctest** and **GoogleBenchmark** Explorer for VSCode +## A **CppUTest**, **Catch2**, **GoogleTest**, **doctest** and **GoogleBenchmark** Explorer for VSCode [![Visual Studio Marketplace](https://img.shields.io/vscode-marketplace/v/matepek.vscode-catch2-test-adapter.svg)](https://marketplace.visualstudio.com/items?itemName=matepek.vscode-catch2-test-adapter) [![GitHub issues](https://img.shields.io/github/issues/matepek/vscode-catch2-test-adapter?color=green)](https://github.com/matepek/vscode-catch2-test-adapter/issues) @@ -8,8 +8,7 @@ [![Gitter](https://badges.gitter.im/CppTestMate/community.svg)](https://gitter.im/CppTestMate/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) This extension allows you to run your [Catch2](https://github.com/catchorg/Catch2), -[Google Test](https://github.com/google/googletest) -and [DOCtest](https://github.com/onqtam/doctest) +[Google Test](https://github.com/google/googletest), [CppUTest](https://github.com/cpputest/cpputest) and [DOCtest](https://github.com/onqtam/doctest) tests using the [Test Explorer for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-test-explorer). It also have basic support for [Google Benchmark](https://github.com/google/benchmark). diff --git a/documents/configuration/test.advancedExecutables.md b/documents/configuration/test.advancedExecutables.md index 8f66861c..8128e9eb 100644 --- a/documents/configuration/test.advancedExecutables.md +++ b/documents/configuration/test.advancedExecutables.md @@ -64,6 +64,7 @@ If it is an object it can contains the following properties: | `gtest` | Object with framework specific settings. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#framework-specific-settings) | | `doctest` | Object with framework specific settings. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#framework-specific-settings) | | `gbenchmark` | Object with framework specific settings. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#framework-specific-settings) | +| `cpputest` | Object with framework specific settings. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#framework-specific-settings) | The `pattern` (or the `executables` used as string or an array of strings) can contain [_search-pattern_](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options). @@ -246,6 +247,10 @@ For example: - `Catch v2.11.1` + `/Catch v(\d+)\.(\d+)\.(\d+)\s?/` -> `[2, 11, 1]` +In case of `cpputest` the helpRegex can be as following. + +- `[-g|sg|xg|xsg groupName]... [-n|sn|xn|xsn testName]...` + ### ignoreTestEnumerationStdErr As the name says test enumeraton will ignore std::err output. diff --git a/package-lock.json b/package-lock.json index 71bd566a..1de896f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,17 @@ { "name": "vscode-catch2-test-adapter", - "version": "3.6.30", + "version": "3.6.31", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-catch2-test-adapter", - "version": "3.6.30", + "version": "3.6.31", "license": "MIT", "dependencies": { "@types/ps-node": "^0.1.1", "gaze": "^1.1.3", + "junit-report-merger": "^2.2.3", "ps-node": "^0.1.6", "tslib": "^2.3.1", "vscode-test-adapter-api": "^1.9.0", @@ -215,7 +216,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -228,7 +228,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -237,7 +236,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -246,6 +244,50 @@ "node": ">= 8" } }, + "node_modules/@oozcitak/dom": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.8.tgz", + "integrity": "sha512-MoOnLBNsF+ok0HjpAvxYxR4piUhRDCEWK0ot3upwOOHYudJd30j6M+LNcE8RKpwfnclAX9T66nXXzkytd29XSw==", + "dependencies": { + "@oozcitak/infra": "1.0.8", + "@oozcitak/url": "1.0.4", + "@oozcitak/util": "8.3.8" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/@oozcitak/infra": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz", + "integrity": "sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==", + "dependencies": { + "@oozcitak/util": "8.3.8" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@oozcitak/url": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz", + "integrity": "sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==", + "dependencies": { + "@oozcitak/infra": "1.0.8", + "@oozcitak/util": "8.3.8" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/@oozcitak/util": { + "version": "8.3.8", + "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz", + "integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ==", + "engines": { + "node": ">=8.0" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.20", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.20.tgz", @@ -541,10 +583,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "16.7.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.10.tgz", - "integrity": "sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==", - "dev": true + "version": "16.7.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.8.tgz", + "integrity": "sha512-8upnoQU0OPzbIkm+ZMM0zCeFCkw2s3mS0IWdx0+AAaWqm4fkBb0UJp8Edl7FVKRamYbpJC/aVsHpKWBIbiC7Zg==" }, "node_modules/@types/ps-node": { "version": "0.1.1", @@ -1085,7 +1126,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -1217,7 +1257,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -1776,9 +1815,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.3.829", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.829.tgz", - "integrity": "sha512-5EXDbvsaLRxS1UOfRr8Hymp3dR42bvBNPgzVuPwUFj3v66bpvDUcNwwUywQUQYn/scz26/3Sgd3fNVGQOlVwvQ==", + "version": "1.3.824", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.824.tgz", + "integrity": "sha512-Fk+5aD0HDi9i9ZKt9n2VPOZO1dQy7PV++hz2wJ/KIn+CvVfu4fny39squHtyVDPuHNuoJGAZIbuReEklqYIqfA==", "dev": true }, "node_modules/emoji-regex": { @@ -2120,7 +2159,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -2271,7 +2309,6 @@ "version": "1.12.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.12.0.tgz", "integrity": "sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -2301,7 +2338,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2663,7 +2699,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -3080,7 +3115,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3098,7 +3132,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -3131,7 +3164,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -3402,6 +3434,37 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/junit-report-merger": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/junit-report-merger/-/junit-report-merger-2.2.3.tgz", + "integrity": "sha512-bddxE3rxoNGAt1LB5eIBbkQKluDMa2WWExsFjjtwaG0s0wQHxvDcD6OYH8t4i/8J/gv5h309NyQK23AiGFSSUA==", + "dependencies": { + "fast-glob": "3.2.6", + "xmlbuilder2": "2.4.1" + }, + "bin": { + "jrm": "cli.js", + "junit-report-merger": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/junit-report-merger/node_modules/fast-glob": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.6.tgz", + "integrity": "sha512-GnLuqj/pvQ7pX8/L4J84nijv6sAnlwvSDpMkJi9i7nPmPxGtRPkBSStfvDW5l6nMdX9VWe+pkKWFTgD+vF2QSQ==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/just-extend": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", @@ -3593,7 +3656,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -3602,7 +3664,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, "dependencies": { "braces": "^3.0.1", "picomatch": "^2.2.3" @@ -4241,7 +4302,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -4397,7 +4457,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -4613,7 +4672,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -4638,7 +4696,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -4881,8 +4938,7 @@ "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "node_modules/string_decoder": { "version": "1.1.1", @@ -5129,7 +5185,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -5348,9 +5403,9 @@ "dev": true }, "node_modules/vsce": { - "version": "1.96.3", - "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.96.3.tgz", - "integrity": "sha512-s3qPnDk0jcbN5FHwBDGGDcFCBeFDbiZ+RS5YGzXtZF1GPoVts6VAOa/iM37MXD+itaiXObvmrEvanrJKshEr+A==", + "version": "1.96.2", + "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.96.2.tgz", + "integrity": "sha512-QM674Bs9z2mtzCoNYZpVjbU9AGzkY6hPeraCCam6m0GH0T0dv/E9G63JmBucR0vrXllGBK9Jt6sCBG1Z6fnTMg==", "dev": true, "dependencies": { "azure-devops-node-api": "^11.0.1", @@ -5900,6 +5955,33 @@ "node": ">=4.0" } }, + "node_modules/xmlbuilder2": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-2.4.1.tgz", + "integrity": "sha512-vliUplZsk5vJnhxXN/mRcij/AE24NObTUm/Zo4vkLusgayO6s3Et5zLEA14XZnY1c3hX5o1ToR0m0BJOPy0UvQ==", + "dependencies": { + "@oozcitak/dom": "1.15.8", + "@oozcitak/infra": "1.0.8", + "@oozcitak/util": "8.3.8", + "@types/node": "*", + "js-yaml": "3.14.0" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/xmlbuilder2/node_modules/js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -6118,7 +6200,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "requires": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -6127,19 +6208,49 @@ "@nodelib/fs.stat": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" }, "@nodelib/fs.walk": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, + "@oozcitak/dom": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.8.tgz", + "integrity": "sha512-MoOnLBNsF+ok0HjpAvxYxR4piUhRDCEWK0ot3upwOOHYudJd30j6M+LNcE8RKpwfnclAX9T66nXXzkytd29XSw==", + "requires": { + "@oozcitak/infra": "1.0.8", + "@oozcitak/url": "1.0.4", + "@oozcitak/util": "8.3.8" + } + }, + "@oozcitak/infra": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz", + "integrity": "sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==", + "requires": { + "@oozcitak/util": "8.3.8" + } + }, + "@oozcitak/url": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz", + "integrity": "sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==", + "requires": { + "@oozcitak/infra": "1.0.8", + "@oozcitak/util": "8.3.8" + } + }, + "@oozcitak/util": { + "version": "8.3.8", + "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz", + "integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ==" + }, "@polka/url": { "version": "1.0.0-next.20", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.20.tgz", @@ -6422,10 +6533,9 @@ "dev": true }, "@types/node": { - "version": "16.7.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.10.tgz", - "integrity": "sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==", - "dev": true + "version": "16.7.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.8.tgz", + "integrity": "sha512-8upnoQU0OPzbIkm+ZMM0zCeFCkw2s3mS0IWdx0+AAaWqm4fkBb0UJp8Edl7FVKRamYbpJC/aVsHpKWBIbiC7Zg==" }, "@types/ps-node": { "version": "0.1.1", @@ -6844,7 +6954,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -6952,7 +7061,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -7369,9 +7477,9 @@ } }, "electron-to-chromium": { - "version": "1.3.829", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.829.tgz", - "integrity": "sha512-5EXDbvsaLRxS1UOfRr8Hymp3dR42bvBNPgzVuPwUFj3v66bpvDUcNwwUywQUQYn/scz26/3Sgd3fNVGQOlVwvQ==", + "version": "1.3.824", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.824.tgz", + "integrity": "sha512-Fk+5aD0HDi9i9ZKt9n2VPOZO1dQy7PV++hz2wJ/KIn+CvVfu4fny39squHtyVDPuHNuoJGAZIbuReEklqYIqfA==", "dev": true }, "emoji-regex": { @@ -7617,8 +7725,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { "version": "1.4.0", @@ -7736,7 +7843,6 @@ "version": "1.12.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.12.0.tgz", "integrity": "sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg==", - "dev": true, "requires": { "reusify": "^1.0.4" } @@ -7763,7 +7869,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -8041,7 +8146,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -8331,8 +8435,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -8344,7 +8447,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -8364,8 +8466,7 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-number-object": { "version": "1.0.6", @@ -8555,6 +8656,29 @@ "universalify": "^2.0.0" } }, + "junit-report-merger": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/junit-report-merger/-/junit-report-merger-2.2.3.tgz", + "integrity": "sha512-bddxE3rxoNGAt1LB5eIBbkQKluDMa2WWExsFjjtwaG0s0wQHxvDcD6OYH8t4i/8J/gv5h309NyQK23AiGFSSUA==", + "requires": { + "fast-glob": "3.2.6", + "xmlbuilder2": "2.4.1" + }, + "dependencies": { + "fast-glob": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.6.tgz", + "integrity": "sha512-GnLuqj/pvQ7pX8/L4J84nijv6sAnlwvSDpMkJi9i7nPmPxGtRPkBSStfvDW5l6nMdX9VWe+pkKWFTgD+vF2QSQ==", + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + } + } + }, "just-extend": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", @@ -8714,14 +8838,12 @@ "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, "micromatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, "requires": { "braces": "^3.0.1", "picomatch": "^2.2.3" @@ -9215,8 +9337,7 @@ "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" }, "pkg-dir": { "version": "4.2.0", @@ -9325,8 +9446,7 @@ "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, "randombytes": { "version": "2.1.0", @@ -9479,8 +9599,7 @@ "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, "rimraf": { "version": "3.0.2", @@ -9495,7 +9614,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "requires": { "queue-microtask": "^1.2.2" } @@ -9662,8 +9780,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "string_decoder": { "version": "1.1.1", @@ -9849,7 +9966,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -10020,9 +10136,9 @@ "dev": true }, "vsce": { - "version": "1.96.3", - "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.96.3.tgz", - "integrity": "sha512-s3qPnDk0jcbN5FHwBDGGDcFCBeFDbiZ+RS5YGzXtZF1GPoVts6VAOa/iM37MXD+itaiXObvmrEvanrJKshEr+A==", + "version": "1.96.2", + "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.96.2.tgz", + "integrity": "sha512-QM674Bs9z2mtzCoNYZpVjbU9AGzkY6hPeraCCam6m0GH0T0dv/E9G63JmBucR0vrXllGBK9Jt6sCBG1Z6fnTMg==", "dev": true, "requires": { "azure-devops-node-api": "^11.0.1", @@ -10422,6 +10538,29 @@ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" }, + "xmlbuilder2": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-2.4.1.tgz", + "integrity": "sha512-vliUplZsk5vJnhxXN/mRcij/AE24NObTUm/Zo4vkLusgayO6s3Et5zLEA14XZnY1c3hX5o1ToR0m0BJOPy0UvQ==", + "requires": { + "@oozcitak/dom": "1.15.8", + "@oozcitak/infra": "1.0.8", + "@oozcitak/util": "8.3.8", + "@types/node": "*", + "js-yaml": "3.14.0" + }, + "dependencies": { + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + } + } + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index ce0ed7c9..92d23791 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "icon": "resources/icon.png", "author": "Mate Pek", "publisher": "matepek", - "version": "3.6.30", + "version": "3.6.31", "license": "MIT", "homepage": "https://github.com/matepek/vscode-catch2-test-adapter", "repository": { @@ -60,7 +60,8 @@ "tslib": "^2.3.1", "vscode-test-adapter-api": "^1.9.0", "vscode-test-adapter-util": "^0.7.1", - "xml2js": "^0.4.23" + "xml2js": "^0.4.23", + "junit-report-merger": "^2.2.3" }, "devDependencies": { "@sentry/node": "^6.11.0", @@ -1323,6 +1324,240 @@ } } } + }, + "cpputest": { + "markdownDescription": "Object with framework specific settings. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#framework-specific-settings)", + "type": "object", + "additionalProperties": false, + "properties": { + "helpRegex": { + "markdownDescription": "A javascript [regex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) which will be used to recognise the framework. Flags: `su`. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md)", + "type": "string", + "minLength": 1 + }, + "prependTestRunningArgs": { + "markdownDescription": "Additinal argument array passed to the executable when it is called for testing. Good for experimental features like `[\"--benchmark-samples\", \"10\"]`. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md)", + "type": "array", + "items": { + "type": "string" + } + }, + "prependTestListingArgs": { + "markdownDescription": "Additinal argument array passed to the executable when it is called for test listing. (Discouraged. Try to use environment variables to pass values.) [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md)", + "type": "array", + "items": { + "type": "string" + } + }, + "debug.enableOutputColouring": { + "markdownDescription": "Sets the colouring of the output for debug session.", + "type": "boolean" + }, + "testGrouping": { + "markdownDescription": "Groups the tests inside the executable. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#testgrouping)", + "additionalProperties": false, + "properties": { + "groupByExecutable": { + "markdownDescription": "Groups tests by the executable file. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#testgrouping)", + "type": "object", + "additionalProperties": false, + "properties": { + "label": { + "markdownDescription": "The label of the test executable. Can contains variables related to `pattern`. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#testgrouping)", + "type": "string" + }, + "description": { + "markdownDescription": "A less prominent text after the `label`. Can contains variables related to `pattern`. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#testgrouping)", + "type": "string" + }, + "groupByExecutable": { + "type": "object" + }, + "groupBySource": { + "type": "object" + }, + "groupByTags": { + "type": "object" + }, + "groupByTagRegex": { + "type": "object" + }, + "groupByRegex": { + "type": "object" + } + } + }, + "groupBySource": { + "markdownDescription": "Groups the tests by the source file. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#testgrouping)", + "type": "object", + "additionalProperties": false, + "properties": { + "label": { + "markdownDescription": "Label of the group. Can be indexed. (`${sourceRelPath}`, `${sourceAbsPath}`). [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#testgrouping)", + "type": "string" + }, + "description": { + "markdownDescription": "Less prominent text next to label. Can be indexed. (`${sourceRelPath}`, `${sourceAbsPath}`). [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#testgrouping)", + "type": "string" + }, + "groupUngroupedTo": { + "markdownDescription": "Ungroupable elements will be grouped under the given value. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#testgrouping)", + "type": "string", + "minLength": 1 + }, + "groupByExecutable": { + "type": "object" + }, + "groupBySource": { + "type": "object" + }, + "groupByTags": { + "type": "object" + }, + "groupByTagRegex": { + "type": "object" + }, + "groupByRegex": { + "type": "object" + } + }, + "required": [ + "sourceIndex" + ] + }, + "groupByTags": { + "markdownDescription": "Group test by tags. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#testgrouping)", + "type": "object", + "additionalProperties": false, + "properties": { + "tags": { + "markdownDescription": "True to group by every exiting combination of the tags. Or it can be an array of tags: `[\"[tag1][tag2]\", \"tag2\", \"tag3\"]` [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#testgrouping)", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "tagFormat": { + "type": "string", + "pattern": "\\$\\{tag\\}" + }, + "groupUngroupedTo": { + "markdownDescription": "Ungroupable elements will be grouped under the given value. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#testgrouping)", + "type": "string", + "minLength": 1 + }, + "groupByExecutable": { + "type": "object" + }, + "groupBySource": { + "type": "object" + }, + "groupByTags": { + "type": "object" + }, + "groupByTagRegex": { + "type": "object" + }, + "groupByRegex": { + "type": "object" + } + } + }, + "groupByTagRegex": { + "markdownDescription": "Groups tests by the first match group of the first matching regex. (`${match}`, `${match_lowercased}`, `${match_upperfirst}`) Example: `[\"(?:good|bad) (apple|peach)\"]` will create 2 groups and put the matched tests inside it. Hint: Grouping starting with \"?:\" won't count as a match group. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#testgrouping)", + "type": "object", + "additionalProperties": false, + "properties": { + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "regexes": { + "markdownDescription": "Groups by the first match group (enclosed by parentheses like this). of the first matching [regex](https://regex101.com/). Example: `[\"(?:good|bad) (apple|peach)\"]` will create 2 groups and put the matched tests inside it. Hint: Grouping starting with \"?:\" won't count as a match group. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#testgrouping)", + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "groupUngroupedTo": { + "markdownDescription": "Ungroupable elements will be grouped under the given value. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#testgrouping)", + "type": "string", + "minLength": 1 + }, + "groupByExecutable": { + "type": "object" + }, + "groupBySource": { + "type": "object" + }, + "groupByTags": { + "type": "object" + }, + "groupByTagRegex": { + "type": "object" + }, + "groupByRegex": { + "type": "object" + } + }, + "required": [ + "regexes" + ] + }, + "groupByRegex": { + "markdownDescription": "Groups tests by the first match group of the first matching regex. (`${match}`, `${match_lowercased}`, `${match_upperfirst}`) Example: `[\"(?:good|bad) (apple|peach)\"]` will create 2 groups and put the matched tests inside it. Hint: Grouping starting with \"?:\" won't count as a match group. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#testgrouping)", + "type": "object", + "additionalProperties": false, + "properties": { + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "regexes": { + "markdownDescription": "Groups by the first match group (enclosed by parentheses like this). of the first matching [regex](https://regex101.com/). Example: `[\"(?:good|bad) (apple|peach)\"]` will create 2 groups and put the matched tests inside it. Hint: Grouping starting with \"?:\" won't count as a match group. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#testgrouping)", + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "groupUngroupedTo": { + "markdownDescription": "Ungroupable elements will be grouped under the given value. [Detail](https://github.com/matepek/vscode-catch2-test-adapter/blob/master/documents/configuration/test.advancedExecutables.md#testgrouping)", + "type": "string", + "minLength": 1 + }, + "groupByExecutable": { + "type": "object" + }, + "groupBySource": { + "type": "object" + }, + "groupByTags": { + "type": "object" + }, + "groupByTagRegex": { + "type": "object" + }, + "groupByRegex": { + "type": "object" + } + }, + "required": [ + "regexes" + ] + } + } + } + } } }, "required": [ diff --git a/src/AdvancedExecutableInterface.ts b/src/AdvancedExecutableInterface.ts index c965fa97..5191c312 100644 --- a/src/AdvancedExecutableInterface.ts +++ b/src/AdvancedExecutableInterface.ts @@ -21,6 +21,7 @@ export interface AdvancedExecutable { catch2?: FrameworkSpecific; gtest?: FrameworkSpecific; doctest?: FrameworkSpecific; + cpputest?: FrameworkSpecific; gbenchmark?: FrameworkSpecific; testGrouping?: TestGrouping; executionWrapper?: ExecutionWrapper; diff --git a/src/Configurations.ts b/src/Configurations.ts index 56718d5f..15a54a1d 100644 --- a/src/Configurations.ts +++ b/src/Configurations.ts @@ -406,6 +406,7 @@ export class Configurations { {}, {}, {}, + {}, ); }; @@ -538,6 +539,7 @@ export class Configurations { this._getFrameworkSpecificSettings(defaultTestGrouping, obj['catch2']), this._getFrameworkSpecificSettings(defaultTestGrouping, obj['gtest']), this._getFrameworkSpecificSettings(defaultTestGrouping, obj['doctest']), + this._getFrameworkSpecificSettings(defaultTestGrouping, obj['cpputest']), this._getFrameworkSpecificSettings(defaultTestGrouping, obj['gbenchmark']), ); }; diff --git a/src/ExecutableConfig.ts b/src/ExecutableConfig.ts index 7b078e05..4f567939 100644 --- a/src/ExecutableConfig.ts +++ b/src/ExecutableConfig.ts @@ -43,6 +43,7 @@ export class ExecutableConfig implements vscode.Disposable { private readonly _catch2: FrameworkSpecific, private readonly _gtest: FrameworkSpecific, private readonly _doctest: FrameworkSpecific, + private readonly _cpputest: FrameworkSpecific, private readonly _gbenchmark: FrameworkSpecific, ) { const createUriSymbol: unique symbol = Symbol('createUri'); @@ -391,6 +392,7 @@ export class ExecutableConfig implements vscode.Disposable { this._catch2, this._gtest, this._doctest, + this._cpputest, this._gbenchmark, this._parallelizationLimit, this._markAsSkipped === true, diff --git a/src/RunnableFactory.ts b/src/RunnableFactory.ts index 54ba2cf4..3dddd1e0 100644 --- a/src/RunnableFactory.ts +++ b/src/RunnableFactory.ts @@ -4,6 +4,7 @@ import { AbstractRunnable } from './AbstractRunnable'; import { Catch2Runnable } from './framework/Catch2Runnable'; import { GoogleTestRunnable } from './framework/GoogleTestRunnable'; import { DOCRunnable } from './framework/DOCRunnable'; +import { CppUTestRunnable } from './framework/CppUTestRunnable'; import { SharedVariables } from './SharedVariables'; import { FrameworkSpecific, RunTask } from './AdvancedExecutableInterface'; import { Version } from './Util'; @@ -24,6 +25,7 @@ export class RunnableFactory { private readonly _catch2: FrameworkSpecific, private readonly _gtest: FrameworkSpecific, private readonly _doctest: FrameworkSpecific, + private readonly _cpputest: FrameworkSpecific, private readonly _gbenchmark: FrameworkSpecific, private readonly _parallelizationLimit: number, private readonly _markAsSkipped: boolean, @@ -156,6 +158,35 @@ export class RunnableFactory { ); } } + { + if (this._cpputest.helpRegex) this._shared.log.info('Custom regex', 'cpputest', this._cpputest.helpRegex); + + const cpputest = runWithHelpRes.stdout.match( + this._cpputest.helpRegex + ? new RegExp(this._cpputest.helpRegex, regexFlags) + : /[-g|sg|xg|xsg groupName]... [-n|sn|xn|xsn testName].../, + ); + if (cpputest) { + return new CppUTestRunnable( + this._shared, + this._rootSuite, + new RunnableProperties( + this._execName, + this._execDescription, + this._varToValue, + this._execPath, + this._execOptions, + this._cpputest, + this._parallelizationLimit, + this._markAsSkipped, + this._runTask, + this._spawner, + this._sourceFileMap, + ), + Promise.resolve(undefined), + ); + } + } { if (this._gbenchmark.helpRegex) diff --git a/src/Suite.ts b/src/Suite.ts index 05431517..e4f056c1 100644 --- a/src/Suite.ts +++ b/src/Suite.ts @@ -161,6 +161,11 @@ export class Suite implements TestSuiteInfo { .map(state => ' - ' + state + ': ' + stateStat[state]) .join('\n'); + const skipOrNotRun = testCount - Object.values(stateStat).reduce((a, b) => a + b, 0); + if (skipOrNotRun !== 0) { + this._additionalTooltip += '\n - NotRan: ' + skipOrNotRun; + } + if (durationSum !== undefined) { const durationStr = milisecToStr(durationSum); diff --git a/src/framework/CppUTestRunnable.ts b/src/framework/CppUTestRunnable.ts new file mode 100644 index 00000000..54049f00 --- /dev/null +++ b/src/framework/CppUTestRunnable.ts @@ -0,0 +1,426 @@ +import * as fs from 'fs-extra'; +import { inspect } from 'util'; +import { mergeFiles } from 'junit-report-merger'; +import { Suite } from '../Suite'; +import { AbstractRunnable, RunnableReloadResult } from '../AbstractRunnable'; +import { CppUTestTest } from './CppUTestTest'; +import { Parser } from 'xml2js'; +import { RunnableProperties } from '../RunnableProperties'; +import { SharedVariables } from '../SharedVariables'; +import { RunningRunnable, ProcessResult } from '../RunningRunnable'; +import { AbstractTest, AbstractTestEvent } from '../AbstractTest'; +import { CancellationFlag, Version } from '../Util'; +import { TestGrouping } from '../TestGroupingInterface'; +import { RootSuite } from '../RootSuite'; + +export class CppUTestRunnable extends AbstractRunnable { + public constructor( + shared: SharedVariables, + rootSuite: RootSuite, + execInfo: RunnableProperties, + version: Promise, + ) { + super(shared, rootSuite, execInfo, 'CppUTest', version); + } + + private getTestGrouping(): TestGrouping { + if (this.properties.testGrouping) { + return this.properties.testGrouping; + } else { + const grouping = { groupByExecutable: this._getGroupByExecutable() }; + return grouping; + } + } + + private async _reloadFromXml(xmlStr: string, cancellationFlag: CancellationFlag): Promise { + const testGrouping = this.getTestGrouping(); + + interface XmlObject { + [prop: string]: any; //eslint-disable-line + } + + let xml: XmlObject = {}; + + new Parser({ explicitArray: true }).parseString(xmlStr, (err: Error, result: Record) => { + if (err) { + throw err; + } else { + xml = result; + } + }); + + const reloadResult = new RunnableReloadResult(); + + const processTestcases = async (testsuite: XmlObject, reloadResult: RunnableReloadResult): Promise => { + const suiteName = testsuite.$.name; + for (let i = 0; i < testsuite.testcase.length; i++) { + if (cancellationFlag.isCancellationRequested) return; + + const testCase = testsuite.testcase[i]; + const testName = testCase.$.name.startsWith('DISABLED_') ? testCase.$.name.substr(9) : testCase.$.name; + const testNameAsId = suiteName + '.' + testCase.$.name; + + const file = testCase.$.file ? await this._resolveSourceFilePath(testCase.$.file) : undefined; + const line = testCase.$.line ? testCase.$.line - 1 : undefined; + + reloadResult.add( + ...(await this._createSubtreeAndAddTest( + testGrouping, + testNameAsId, + testName, + file, + [suiteName], + (parent: Suite) => new CppUTestTest(this._shared, this, parent, testNameAsId, testName, file, line), + (old: AbstractTest) => (old as CppUTestTest).update(testNameAsId, file, line), + )), + ); + } + }; + + if (xml.testsuites !== undefined) { + for (let i = 0; i < xml.testsuites.testsuite.length; ++i) { + await processTestcases(xml.testsuites.testsuite[i], reloadResult).catch(err => + this._shared.log.info('Error', err), + ); + } + } else { + await processTestcases(xml.testsuite, reloadResult); + } + + return reloadResult; + } + + private async _reloadFromString( + stdOutStr: string, + cancellationFlag: CancellationFlag, + ): Promise { + const testGrouping = this.getTestGrouping(); + const lines = stdOutStr.split(' '); + + const reloadResult = new RunnableReloadResult(); + + for (let i = 0; i < lines.length; i++) { + if (cancellationFlag.isCancellationRequested) return reloadResult; + const suiteName = lines[i].split('.')[0]; + const testName = lines[i].split('.')[1]; + const testNameAsId = suiteName + '.' + testName; + + reloadResult.add( + ...(await this._createSubtreeAndAddTest( + testGrouping, + testNameAsId, + testName, + undefined, + [suiteName], + (parent: Suite) => new CppUTestTest(this._shared, this, parent, testNameAsId, testName, undefined, undefined), + (old: AbstractTest) => (old as CppUTestTest).update(testNameAsId, undefined, undefined), + )), + ); + } + return reloadResult; + } + + protected async _reloadChildren(cancellationFlag: CancellationFlag): Promise { + const cacheFile = this.properties.path + '.TestMate.testListCache.xml'; + + if (this._shared.enabledTestListCaching) { + try { + const cacheStat = await fs.stat(cacheFile); + const execStat = await fs.stat(this.properties.path); + + if (cacheStat.size > 0 && cacheStat.mtime > execStat.mtime) { + this._shared.log.info('loading from cache: ', cacheFile); + const xmlStr = await fs.readFile(cacheFile, 'utf8'); + + return await this._reloadFromXml(xmlStr, cancellationFlag); + } + } catch (e) { + this._shared.log.info('coudnt use cache', e); + } + } + + const args = this.properties.prependTestListingArgs.concat(['-ln']); + + this._shared.log.info('discovering tests', this.properties.path, args, this.properties.options.cwd); + const cppUTestListOutput = await this.properties.spawner.spawnAsync( + this.properties.path, + args, + this.properties.options, + 30000, + ); + + if (cppUTestListOutput.stderr && !this.properties.ignoreTestEnumerationStdErr) { + this._shared.log.warn('reloadChildren -> cppUTestListOutput.stderr: ', cppUTestListOutput); + return await this._createAndAddUnexpectedStdError(cppUTestListOutput.stdout, cppUTestListOutput.stderr); + } + + if (cppUTestListOutput.stdout.length === 0) { + this._shared.log.debug(cppUTestListOutput); + throw Error('stoud is empty'); + } + + const result = this._reloadFromString(cppUTestListOutput.stdout, cancellationFlag); + + if (this._shared.enabledTestListCaching) { + //Generate xmls folder + const junitXmlsFolderPath = this.properties.path + '_junit_xmls'; + fs.mkdir(junitXmlsFolderPath) + .then(() => this._shared.log.info('junit-xmls folder created', junitXmlsFolderPath)) + .catch(err => this._shared.log.error('error creating xmls folder: ', junitXmlsFolderPath, err)); + //Generate xml files + const args = this.properties.prependTestListingArgs.concat(['-ojunit']); + const options = { cwd: junitXmlsFolderPath }; + await this.properties.spawner + .spawnAsync(this.properties.path, args, options, 30000) + .then(() => this._shared.log.info('create cpputest xmls', this.properties.path, args, options.cwd)); + //Merge xmls into single xml + fs.readdir(junitXmlsFolderPath, (err, files) => { + if (files.length > 1) { + mergeFiles(cacheFile, [junitXmlsFolderPath + '/*.xml']) + .then(() => this._shared.log.info('cache xml written', cacheFile)) + .catch(err => this._shared.log.warn('combine xml cache file could not create: ', cacheFile, err)); + } else { + fs.copyFile(junitXmlsFolderPath + '/' + files[0], cacheFile); + } + }); + //Delete xmls folder + fs.remove(junitXmlsFolderPath) + .then(() => this._shared.log.info('junit-xmls folder deleted', junitXmlsFolderPath)) + .catch(err => this._shared.log.error('error deleting xmls folder: ', junitXmlsFolderPath, err)); + } + return result; + } + + protected _getRunParamsInner(childrenToRun: readonly Readonly[]): string[] { + const execParams: string[] = []; + childrenToRun.forEach(t => { + execParams.push(`TEST(${t.testNameAsId.split('.')[0]}, ${t.testNameAsId.split('.')[1]})`); + }); + execParams.push('-c'); + execParams.push('-v'); + return execParams; + } + + protected _getDebugParamsInner(childrenToRun: readonly Readonly[]): string[] { + const execParams: string[] = []; + childrenToRun.forEach(t => { + execParams.push(`TEST(${t.testNameAsId.split('.')[0]}, ${t.testNameAsId.split('.')[1]})`); + }); + execParams.push('-c'); + execParams.push('-v'); + + return execParams; + } + + protected _handleProcess(testRunId: string, runInfo: RunningRunnable): Promise { + const data = new (class { + public stdoutAndErrBuffer = ''; + public currentTestCaseNameFull: string | undefined = undefined; + public currentChild: AbstractTest | undefined = undefined; + public route: Suite[] = []; + public unprocessedTestCases: string[] = []; + public processedTestCases: AbstractTest[] = []; + })(); + + const testBeginRe = /^TEST\(((.+?)\,\s*(.+?))\)/m; + const rngSeed: number | undefined = typeof this._shared.rngSeed === 'number' ? this._shared.rngSeed : undefined; + + return new Promise(resolve => { + const chunks: string[] = []; + const processChunk = (chunk: string): void => { + chunks.push(chunk); + data.stdoutAndErrBuffer = data.stdoutAndErrBuffer + chunk; + let invariant = 99999; + do { + if (runInfo.cancellationToken.isCancellationRequested) return; + + if (data.currentTestCaseNameFull === undefined) { + const m = data.stdoutAndErrBuffer.match(testBeginRe); + if (m == null) return; + + data.currentTestCaseNameFull = m[2] + '.' + m[3]; + + const test = this._findTest(v => v.testNameAsId == data.currentTestCaseNameFull); + + if (test) { + const route = [...test.route()]; + this.sendMinimalEventsIfNeeded(testRunId, data.route, route); + data.route = route; + + data.currentChild = test; + this._shared.log.info('Test', data.currentChild.testNameAsId, 'has started.'); + this._shared.sendTestRunEvent(data.currentChild.getStartEvent(testRunId)); + } else { + this._shared.log.info('TestCase not found in children', data.currentTestCaseNameFull); + } + + data.stdoutAndErrBuffer = data.stdoutAndErrBuffer.substr(m.index!); + } else { + const testEndRe = / - \d+ ms$/m; + const m = data.stdoutAndErrBuffer.match(testEndRe); + + if (m == null) return; + + const testCase = data.stdoutAndErrBuffer.substring(0, m.index! + m[0].length); + + if (data.currentChild !== undefined) { + this._shared.log.info('Test ', data.currentChild.testNameAsId, 'has finished.'); + try { + const ev = data.currentChild.parseAndProcessTestCase( + testRunId, + testCase, + rngSeed, + runInfo.timeout, + undefined, + ); + this._shared.sendTestRunEvent(ev); + data.processedTestCases.push(data.currentChild); + } catch (e) { + this._shared.log.error('parsing and processing test', e, data); + + data.currentChild.lastRunEvent = { + testRunId, + type: 'test', + test: data.currentChild.id, + state: 'errored', + message: [ + 'šŸ˜± Unexpected error under parsing output !! Error: ' + inspect(e), + 'Consider opening an issue: https://github.com/matepek/vscode-catch2-test-adapter/issues/new/choose', + `Please attach the output of: "${runInfo.process.spawnfile} ${runInfo.process.spawnargs}"`, + '=== Output ===', + testCase, + '==============', + 'ā¬‡ stdoutAndErrBuffer:', + data.stdoutAndErrBuffer, + 'ā¬† stdoutAndErrBuffer', + 'ā¬‡ std::cout:', + runInfo.process.stdout, + 'ā¬† std::cout', + 'ā¬‡ std::cerr:', + runInfo.process.stderr, + 'ā¬† std::cerr', + ].join('\n'), + }; + + this._shared.sendTestRunEvent(data.currentChild.lastRunEvent); + } + } else { + this._shared.log.info('Test case found without TestInfo: ', this, '; ' + testCase); + data.unprocessedTestCases.push(testCase); + } + + data.currentTestCaseNameFull = undefined; + data.currentChild = undefined; + data.stdoutAndErrBuffer = data.stdoutAndErrBuffer.substr(m.index! + m[0].length); + } + } while (data.stdoutAndErrBuffer.length > 0 && --invariant > 0); + if (invariant == 0) { + this._shared.log.error('invariant==0', this, runInfo, data, chunks); + resolve(ProcessResult.error('Possible infinite loop of this extension')); + runInfo.killProcess(); + } + }; + + runInfo.process.stdout.on('data', (chunk: Uint8Array) => processChunk(chunk.toLocaleString())); + runInfo.process.stderr.on('data', (chunk: Uint8Array) => processChunk(chunk.toLocaleString())); + + runInfo.process.once('close', (code: number | null, signal: string | null) => { + if (runInfo.cancellationToken.isCancellationRequested) { + resolve(ProcessResult.ok()); + } else { + if (code !== null && code !== undefined) resolve(ProcessResult.createFromErrorCode(code)); + else if (signal !== null && signal !== undefined) resolve(ProcessResult.createFromSignal(signal)); + else resolve(ProcessResult.error('unknown sfngvdlfkxdvgn')); + } + }); + }) + .catch((reason: Error) => { + // eslint-disable-next-line + if ((reason as any).code === undefined) this._shared.log.exceptionS(reason); + + return new ProcessResult(reason); + }) + .then((result: ProcessResult) => { + result.error && this._shared.log.info(result.error.toString(), result, runInfo, this, data); + + if (data.currentTestCaseNameFull !== undefined) { + if (data.currentChild !== undefined) { + this._shared.log.info('data.currentChild !== undefined: ', data); + + let ev: AbstractTestEvent; + + if (runInfo.cancellationToken.isCancellationRequested) { + ev = data.currentChild.getCancelledEvent(testRunId, data.stdoutAndErrBuffer); + } else if (runInfo.timeout !== null) { + ev = data.currentChild.getTimeoutEvent(testRunId, runInfo.timeout); + } else { + ev = data.currentChild.getFailedEventBase(testRunId); + + ev.message = 'šŸ˜± Unexpected error !!'; + + if (result.error) { + ev.state = 'errored'; + ev.message += '\n' + result.error.message; + } + + ev.message += data.stdoutAndErrBuffer ? `\n\n>>>${data.stdoutAndErrBuffer}<<<` : ''; + } + + data.currentChild.lastRunEvent = ev; + this._shared.sendTestRunEvent(ev); + } else { + this._shared.log.warn('data.inTestCase: ', data); + } + } + + this.sendMinimalEventsIfNeeded(testRunId, data.route, []); + data.route = []; + + const isTestRemoved = + runInfo.timeout === null && + !runInfo.cancellationToken.isCancellationRequested && + result.error === undefined && + data.processedTestCases.length < runInfo.childrenToRun.length; + + if (data.unprocessedTestCases.length > 0 || isTestRemoved) { + this.reloadTests(this._shared.taskPool, runInfo.cancellationToken).then( + () => { + // we have test results for the newly detected tests + // after reload we can set the results + const events: AbstractTestEvent[] = []; + + for (let i = 0; i < data.unprocessedTestCases.length; i++) { + const testCase = data.unprocessedTestCases[i]; + + const m = testCase.match(testBeginRe); + if (m == null) break; + + const testNameAsId = m[1]; + + const currentChild = this._findTest(v => v.compare(testNameAsId)); + + if (currentChild === undefined) break; + try { + const ev = currentChild.parseAndProcessTestCase( + testRunId, + testCase, + rngSeed, + runInfo.timeout, + undefined, + ); + events.push(ev); + } catch (e) { + this._shared.log.error('parsing and processing test failed', e, testCase); + } + } + events.length && this._shared.sendTestEvents(events); + }, + (reason: Error) => { + // Suite possibly deleted: It is a dead suite. + this._shared.log.error('reloading-error: ', reason); + }, + ); + } + }); + } +} diff --git a/src/framework/CppUTestTest.ts b/src/framework/CppUTestTest.ts new file mode 100644 index 00000000..80f6a9bb --- /dev/null +++ b/src/framework/CppUTestTest.ts @@ -0,0 +1,109 @@ +import { AbstractTest, AbstractTestEvent, SharedWithTest } from '../AbstractTest'; +import { Suite } from '../Suite'; +import { AbstractRunnable } from '../AbstractRunnable'; +import { TestEventBuilder } from '../TestEventBuilder'; + +export class CppUTestTest extends AbstractTest { + public constructor( + shared: SharedWithTest, + runnable: AbstractRunnable, + parent: Suite, + testNameAsId: string, + label: string, + file: string | undefined, + line: number | undefined, + ) { + super( + shared, + runnable, + parent, + testNameAsId, + label, + file, + line, + testNameAsId.startsWith('IGNORE_TEST'), + undefined, + [], + undefined, + undefined, + undefined, + ); + } + + private static _isSkipped(tags: string[], testNameAsId: string): boolean { + return tags.some((v: string) => v.startsWith('.') || v == 'hide' || v == '!hide') || testNameAsId.startsWith('./'); + } + + public update(testNameAsId: string | undefined, file: string | undefined, line: number | undefined): boolean { + const label = testNameAsId ? testNameAsId : this._label; + return this._updateBase( + label, + file, + line, + CppUTestTest._isSkipped(this._tags, this.testNameAsId), + this._tags, + this._testDescription, + undefined, + undefined, + this._staticEvent, + ); + } + + public compare(testNameAsId: string): boolean { + return this.testNameAsId === testNameAsId; + } + + public static readonly failureRe = /^((.+)[:\(]([0-9]+)\)?): ((Failure|EXPECT_CALL|error: )(.*))$/; + + public parseAndProcessTestCase( + testRunId: string, + output: string, + rngSeed: number | undefined, + timeout: number | null, + stderr: string | undefined, //eslint-disable-line + ): AbstractTestEvent { + if (timeout !== null) { + const ev = this.getTimeoutEvent(testRunId, timeout); + this.lastRunEvent = ev; + return ev; + } + + try { + const lines = output.split(/\r?\n/); + + const eventBuilder = new TestEventBuilder(this, testRunId); + + const runDuration = lines[lines.length - 1].match(/([0-9]+) ms/); + eventBuilder.setDurationMilisec(runDuration ? Number(runDuration[1]) : undefined); + + let failedTest: RegExpMatchArray | null = null; + //Ignored tests will be never gets here because tests were run individually + const isSkipped = lines[0].match(/^IGNORE_TEST/); + if (isSkipped) eventBuilder.skipped(); + if (lines.length > 1) { + failedTest = lines[1].match(CppUTestTest.failureRe); + } + if (failedTest === null) eventBuilder.passed(); + else eventBuilder.failed(); + + if (lines.length > 1 && failedTest !== null) { + const filePath = failedTest[2].split('/').pop(); + const lineNumber = Number(failedTest[3]) - 1; + const expected = lines.find(value => /^\texpected/.test(value)); + const actual = lines.find(value => /^\tbut was/.test(value)); + eventBuilder.appendDecorator(filePath, lineNumber, [expected ? expected : '', actual ? actual : '']); + } + + const event = eventBuilder.build(output.replace(/\): error: /g, '): error: \n')); + + return event; + } catch (e) { + this._shared.log.exceptionS(e, output); + + const ev = this.getFailedEventBase(testRunId); + ev.message = 'Unexpected error: ' + e; + + return ev; + } + } +} diff --git a/test/ExecutableConfig.test.ts b/test/ExecutableConfig.test.ts index 4e60a341..403d2ea1 100644 --- a/test/ExecutableConfig.test.ts +++ b/test/ExecutableConfig.test.ts @@ -30,6 +30,7 @@ describe(path.basename(__filename), function () { {}, {}, {}, + {}, ); specify('_pathProcessor with posix name', async function () { diff --git a/test/RunnableFactory.test.ts b/test/RunnableFactory.test.ts index 390ca7a7..7d2b01bd 100644 --- a/test/RunnableFactory.test.ts +++ b/test/RunnableFactory.test.ts @@ -46,6 +46,7 @@ describe(path.basename(__filename), function () { {}, {}, {}, + {}, 1, false, {}, @@ -90,6 +91,7 @@ describe(path.basename(__filename), function () { {}, {}, {}, + {}, 1, false, {}, diff --git a/test/cpp/CMakeLists.txt b/test/cpp/CMakeLists.txt index 6ad32cb2..3cbfc6bf 100644 --- a/test/cpp/CMakeLists.txt +++ b/test/cpp/CMakeLists.txt @@ -24,4 +24,5 @@ add_subdirectory("catch2") add_subdirectory("gtest") add_subdirectory("doctest") add_subdirectory("gbenchmark") +add_subdirectory("cpputest") add_subdirectory("misc") \ No newline at end of file diff --git a/test/cpp/cpputest/CMakeLists.txt b/test/cpp/cpputest/CMakeLists.txt new file mode 100644 index 00000000..f393c084 --- /dev/null +++ b/test/cpp/cpputest/CMakeLists.txt @@ -0,0 +1,3 @@ +include("CppUTest.cmake") + +add_cpputest_with_main(cpputest1 "cpputest1.cpp") \ No newline at end of file diff --git a/test/cpp/cpputest/CppUTest.cmake b/test/cpp/cpputest/CppUTest.cmake new file mode 100644 index 00000000..d13b92e6 --- /dev/null +++ b/test/cpp/cpputest/CppUTest.cmake @@ -0,0 +1,13 @@ +include(FetchContent) + +FetchContent_Declare(cpputest + GIT_REPOSITORY https://github.com/cpputest/cpputest.git + GIT_TAG latest-passing-build) + +set(TESTS OFF CACHE BOOL "Switch off CppUTest Test build") +FetchContent_MakeAvailable(cpputest) + +function(add_cpputest_with_main target cpp_file) + add_executable(${target} "${cpp_file}") + target_link_libraries(${target} PRIVATE CppUTest CppUTestExt) +endfunction() \ No newline at end of file diff --git a/test/cpp/cpputest/cpputest1.cpp b/test/cpp/cpputest/cpputest1.cpp new file mode 100644 index 00000000..1e743330 --- /dev/null +++ b/test/cpp/cpputest/cpputest1.cpp @@ -0,0 +1,35 @@ +#include +#include +#include +#include +#include + +#include "CppUTest/TestHarness.h" +#include "CppUTest/CommandLineTestRunner.h" +using namespace std; + +TEST_GROUP(FirstTestGroup) +{ +}; + +TEST(FirstTestGroup, FirstTest) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); +} + +TEST(FirstTestGroup, SecondTest) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + CHECK(false); +} + +TEST(FirstTestGroup, ThirdTest) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + //FAIL("Fail me!"); +} + +int main(int argc, char* argv[]) +{ + return CommandLineTestRunner::RunAllTests(argc, argv); +} \ No newline at end of file diff --git a/test/framework/CppUTestRunnable.test.ts b/test/framework/CppUTestRunnable.test.ts new file mode 100644 index 00000000..c9cc9c16 --- /dev/null +++ b/test/framework/CppUTestRunnable.test.ts @@ -0,0 +1,216 @@ +import * as assert from 'assert'; +import * as pathlib from 'path'; +import { Imitation, SharedVariables } from '../Common'; +import { CppUTestRunnable } from '../../src/framework/CppUTestRunnable'; +import { RootSuite } from '../../src/RootSuite'; +import { RunnableProperties } from '../../src/RunnableProperties'; +import { DefaultSpawner } from '../../src/Spawner'; +import { EOL } from 'os'; + +/// + +describe(pathlib.basename(__filename), function () { + const sharedVariables = new SharedVariables(); + + const runnableProperties = new RunnableProperties( + 'name', + undefined, + [ + { resolve: '${relDirpath}', rule: 'relDirpath' }, + { resolve: '${osPathSep}', rule: 'osPathSep' }, + ], + 'path', + {}, + {}, + 1, + false, + {}, + new DefaultSpawner(), + {}, + ); + + const createCppUTestRunnable = (): { runnable: CppUTestRunnable; root: RootSuite } => { + const root = new RootSuite(undefined, sharedVariables); + return { + root, + runnable: new CppUTestRunnable( + sharedVariables, + root, + runnableProperties, + // 'cpputest_', + Promise.resolve(undefined), + ), + }; + }; + + let imitation: Imitation; + + before(function () { + imitation = new Imitation(); + }); + + afterEach(function () { + imitation.resetToCallThrough(); + }); + + after(function () { + imitation.restore(); + }); + + context('Testing _reloadFromString', function () { + it('should reload ex.1', async function () { + const { root, runnable } = createCppUTestRunnable(); + assert.strictEqual(runnable.tests.size, 0); + + const testListOutput: string[] = [ + 'TeamCityOutputTest.TestNameEscaped_Fail', + 'TeamCityOutputTest.TestNameEscaped_Ignore', + 'TeamCityOutputTest.TestNameEscaped_End', + 'TeamCityOutputTest.TestNameEscaped_Start', + 'TeamCityOutputTest.TestGroupEscaped_End', + 'UTestPlatformsTest_PlatformSpecificRunTestInASeperateProcess.MultipleTestsInSeparateProcessAreCountedProperly', + 'UTestPlatformsTest_PlatformSpecificRunTestInASeperateProcess.CallToWaitPidFailedInSeparateProcessWorks', + 'UTestPlatformsTest_PlatformSpecificRunTestInASeperateProcess.CallToWaitPidStopsAndReportsAnErrorAfter20TimesRetry', + 'SimpleMutexTest.LockUnlockTest', + 'SimpleMutexTest.CreateAndDestroy', + 'UtestShellPointerArrayTest.reverse', + ]; + const res = await runnable['_reloadFromString'](testListOutput.join(' '), { isCancellationRequested: false }); + + const tests = [...res.tests].sort((a, b) => a.testNameAsId.localeCompare(b.testNameAsId)); + + assert.strictEqual(tests.length, 11); + assert.strictEqual(tests[0].testNameAsId, 'SimpleMutexTest.CreateAndDestroy'); + assert.strictEqual(tests[0].label, 'CreateAndDestroy'); + assert.strictEqual(tests[0].file, undefined); + assert.strictEqual(tests[0].line, undefined); + assert.strictEqual(tests[0].skipped, false); + assert.strictEqual(tests[0].getStaticEvent('1'), undefined); + assert.strictEqual(tests[1].testNameAsId, 'SimpleMutexTest.LockUnlockTest'); + assert.strictEqual(tests[1].label, 'LockUnlockTest'); + assert.strictEqual(tests[1].file, undefined); + assert.strictEqual(tests[1].line, undefined); + assert.strictEqual(tests[1].skipped, false); + assert.strictEqual(tests[1].getStaticEvent('1'), undefined); + assert.strictEqual(tests[2].testNameAsId, 'TeamCityOutputTest.TestGroupEscaped_End'); + assert.strictEqual(tests[2].label, 'TestGroupEscaped_End'); + assert.strictEqual(tests[2].file, undefined); + assert.strictEqual(tests[2].line, undefined); + assert.strictEqual(tests[2].skipped, false); + assert.strictEqual(tests[2].getStaticEvent('1'), undefined); + assert.strictEqual(tests[3].testNameAsId, 'TeamCityOutputTest.TestNameEscaped_End'); + assert.strictEqual(tests[3].label, 'TestNameEscaped_End'); + assert.strictEqual(tests[3].file, undefined); + assert.strictEqual(tests[3].line, undefined); + assert.strictEqual(tests[3].skipped, false); + assert.strictEqual(tests[3].getStaticEvent('1'), undefined); + assert.strictEqual(tests[4].testNameAsId, 'TeamCityOutputTest.TestNameEscaped_Fail'); + assert.strictEqual(tests[4].label, 'TestNameEscaped_Fail'); + assert.strictEqual(tests[4].file, undefined); + assert.strictEqual(tests[4].line, undefined); + assert.strictEqual(tests[4].skipped, false); + assert.strictEqual(tests[4].getStaticEvent('1'), undefined); + assert.strictEqual(tests[5].testNameAsId, 'TeamCityOutputTest.TestNameEscaped_Ignore'); + assert.strictEqual(tests[5].label, 'TestNameEscaped_Ignore'); + assert.strictEqual(tests[5].file, undefined); + assert.strictEqual(tests[5].line, undefined); + assert.strictEqual(tests[5].skipped, false); + assert.strictEqual(tests[5].getStaticEvent('1'), undefined); + assert.strictEqual(tests[6].testNameAsId, 'TeamCityOutputTest.TestNameEscaped_Start'); + assert.strictEqual(tests[6].label, 'TestNameEscaped_Start'); + assert.strictEqual(tests[6].file, undefined); + assert.strictEqual(tests[6].line, undefined); + assert.strictEqual(tests[6].skipped, false); + assert.strictEqual(tests[6].getStaticEvent('1'), undefined); + assert.strictEqual( + tests[7].testNameAsId, + 'UTestPlatformsTest_PlatformSpecificRunTestInASeperateProcess.CallToWaitPidFailedInSeparateProcessWorks', + ); + assert.strictEqual(tests[7].label, 'CallToWaitPidFailedInSeparateProcessWorks'); + assert.strictEqual(tests[7].file, undefined); + assert.strictEqual(tests[7].line, undefined); + assert.strictEqual(tests[7].skipped, false); + assert.strictEqual(tests[7].getStaticEvent('1'), undefined); + assert.strictEqual( + tests[8].testNameAsId, + 'UTestPlatformsTest_PlatformSpecificRunTestInASeperateProcess.CallToWaitPidStopsAndReportsAnErrorAfter20TimesRetry', + ); + assert.strictEqual(tests[8].label, 'CallToWaitPidStopsAndReportsAnErrorAfter20TimesRetry'); + assert.strictEqual(tests[8].file, undefined); + assert.strictEqual(tests[8].line, undefined); + assert.strictEqual(tests[8].skipped, false); + assert.strictEqual(tests[8].getStaticEvent('1'), undefined); + assert.strictEqual( + tests[9].testNameAsId, + 'UTestPlatformsTest_PlatformSpecificRunTestInASeperateProcess.MultipleTestsInSeparateProcessAreCountedProperly', + ); + assert.strictEqual(tests[9].label, 'MultipleTestsInSeparateProcessAreCountedProperly'); + assert.strictEqual(tests[9].file, undefined); + assert.strictEqual(tests[9].line, undefined); + assert.strictEqual(tests[9].skipped, false); + assert.strictEqual(tests[9].getStaticEvent('1'), undefined); + assert.strictEqual(tests[10].testNameAsId, 'UtestShellPointerArrayTest.reverse'); + assert.strictEqual(tests[10].label, 'reverse'); + assert.strictEqual(tests[10].file, undefined); + assert.strictEqual(tests[10].line, undefined); + assert.strictEqual(tests[10].skipped, false); + assert.strictEqual(tests[10].getStaticEvent('1'), undefined); + + assert.strictEqual(root.children.length, 1); + const suite1 = root.children[0]; + assert.strictEqual(suite1.label, 'name'); + if (suite1.type === 'suite') { + assert.strictEqual(suite1.children.length, 11); + } else { + assert.strictEqual(suite1.type, 'suite'); + } + }); + }); + + context('Testing _reloadFromXml', function () { + it('should reload ex.1', async function () { + const { root, runnable } = createCppUTestRunnable(); + + const testOutput: string[] = [ + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + ',', + ]; + const res = await runnable['_reloadFromXml'](testOutput.join(EOL), { isCancellationRequested: false }); + + const tests = [...res.tests].sort((a, b) => a.testNameAsId.localeCompare(b.testNameAsId)); + + assert.strictEqual(tests.length, 2); + + assert.strictEqual(tests[0].testNameAsId, 'FirstTestGroup.FirstTest'); + assert.strictEqual(tests[0].label, 'FirstTest'); + assert.strictEqual(tests[0].file, pathlib.normalize('/mnt/c/Users/testcppcpputestcpputest1.cpp')); + assert.strictEqual(tests[0].line, 14); + assert.strictEqual(tests[0].skipped, false); + assert.strictEqual(tests[0].getStaticEvent('1'), undefined); + assert.strictEqual(tests[1].testNameAsId, 'FirstTestGroup.SecondTest'); + assert.strictEqual(tests[1].label, 'SecondTest'); + assert.strictEqual(tests[1].file, pathlib.normalize('/mnt/c/Users/testcppcpputestcpputest1.cpp')); + assert.strictEqual(tests[1].line, 19); + assert.strictEqual(tests[1].skipped, false); + assert.strictEqual(tests[1].getStaticEvent('1'), undefined); + + assert.strictEqual(root.children.length, 1); + const suite1 = root.children[0]; + assert.strictEqual(suite1.label, 'name'); + if (suite1.type === 'suite') { + assert.strictEqual(suite1.children.length, 2); + } else { + assert.strictEqual(suite1.type, 'suite'); + } + }); + }); +}); diff --git a/test/framework/CppUTestTest.test.ts b/test/framework/CppUTestTest.test.ts new file mode 100644 index 00000000..25615158 --- /dev/null +++ b/test/framework/CppUTestTest.test.ts @@ -0,0 +1,193 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import { CppUTestTest } from '../../src/framework/CppUTestTest'; +import { AbstractRunnable } from '../../src/AbstractRunnable'; +import { Suite } from '../../src/Suite'; +import { EOL } from 'os'; +import { TestRunEvent } from '../../src/SharedVariables'; +import { logger } from '../LogOutputContent.test'; + +describe(path.basename(__filename), function () { + const cpputest: CppUTestTest = new CppUTestTest( + { + log: logger, + }, + null as unknown as AbstractRunnable, + null as unknown as Suite, + 'TestCase.TestName', + 'TestName', + 'gtest.cpp', + 11, + ); + + it('parses failed test', function () { + const output = [ + 'TEST(UtestShell, PassedCheckEqualWillIncreaseTheAmountOfChecks)', + '/workspaces/cpputest-4.0/tests/CppUTest/UtestTest.cpp:90: error: Failure in TEST(UtestShell, PassedCheckEqualWillIncreaseTheAmountOfChecks)', + 'LONGS_EQUAL(10, fixture.getCheckCount()) failed', + '\texpected <10 (0xa)>', + '\tbut was < 1 (0x1)>', + ' - 47 ms', + ].join(EOL); + + const ev = cpputest.parseAndProcessTestCase('runid', output, 42, null, ''); + + const expected: TestRunEvent = { + testRunId: 'runid', + type: 'test', + test: cpputest.id, + message: output, + state: 'failed', + description: '(47ms)', + tooltip: 'Name: TestCase.TestName\nā±Duration: 47ms', + decorations: [ + { + file: 'UtestTest.cpp', + hover: '\texpected <10 (0xa)>\n\tbut was < 1 (0x1)>', + line: 89, + message: 'ā¬… expected <10 (0xa)>; but was < 1 (0x1)>', + }, + ], + }; + + assert.strictEqual(cpputest.description, ev.description); + assert.strictEqual(cpputest.tooltip, ev.tooltip); + assert.strictEqual(cpputest.lastRunMilisec, 47); + assert.deepStrictEqual(ev, expected); + assert.deepStrictEqual(cpputest.lastRunEvent, expected); + }); + + it('parses another failed test', function () { + const output = [ + 'TEST(read_rssi, min_rssi_with_min_gain)', + '/workspaces/cpputest-4.0/tests/CppUTest/test_read_rssi.cpp:176: error: Failure in TEST(read_rssi, min_rssi_with_min_gain)', + '\texpected <-1984>', + '\tbut was <-1987>', + 'difference starts at position 4 at: < -1987 >', + ' ^', + '', + '- 47 ms', + ].join(EOL); + + const ev = cpputest.parseAndProcessTestCase('runid', output, 42, null, ''); + + const expected: TestRunEvent = { + testRunId: 'runid', + type: 'test', + test: cpputest.id, + message: output, + state: 'failed', + description: '(47ms)', + tooltip: 'Name: TestCase.TestName\nā±Duration: 47ms', + decorations: [ + { + file: 'test_read_rssi.cpp', + hover: '\texpected <-1984>\n\tbut was <-1987>', + line: 175, + message: 'ā¬… expected <-1984>; but was <-1987>', + }, + ], + }; + + assert.strictEqual(cpputest.description, ev.description); + assert.strictEqual(cpputest.tooltip, ev.tooltip); + assert.strictEqual(cpputest.lastRunMilisec, 47); + assert.deepStrictEqual(ev, expected); + assert.deepStrictEqual(cpputest.lastRunEvent, expected); + }); + + it('parses passed test', function () { + const output = ['TEST(UtestShell, compareDoubles) - 30 ms'].join(EOL); + + const ev = cpputest.parseAndProcessTestCase('runid', output, 42, null, ''); + + const expected: TestRunEvent = { + testRunId: 'runid', + type: 'test', + test: cpputest.id, + message: output, + state: 'passed', + description: '(30ms)', + tooltip: 'Name: TestCase.TestName\nā±Duration: 30ms', + decorations: [], + }; + + assert.strictEqual(cpputest.description, ev.description); + assert.strictEqual(cpputest.tooltip, ev.tooltip); + assert.strictEqual(cpputest.lastRunMilisec, 30); + assert.deepStrictEqual(ev, expected); + assert.deepStrictEqual(cpputest.lastRunEvent, expected); + }); + + it('parses passed test with multilines', function () { + const output = ['TEST(UtestShell, compareDoubles)', ' - 30 ms'].join(EOL); + + const ev = cpputest.parseAndProcessTestCase('runid', output, 42, null, ''); + + const expected: TestRunEvent = { + testRunId: 'runid', + type: 'test', + test: cpputest.id, + message: output, + state: 'passed', + description: '(30ms)', + tooltip: 'Name: TestCase.TestName\nā±Duration: 30ms', + decorations: [], + }; + + assert.strictEqual(cpputest.description, ev.description); + assert.strictEqual(cpputest.tooltip, ev.tooltip); + assert.strictEqual(cpputest.lastRunMilisec, 30); + assert.deepStrictEqual(ev, expected); + assert.deepStrictEqual(cpputest.lastRunEvent, expected); + }); + + it('parses passed multilines test with extra texts', function () { + const output = [ + 'TEST(UtestShell, compareDoubles) Assertion fail /workspaces/user/app/base/system/workqueue.c:34', + ' - 10 ms', + ].join(EOL); + + const ev = cpputest.parseAndProcessTestCase('runid', output, 42, null, ''); + + const expected: TestRunEvent = { + testRunId: 'runid', + type: 'test', + test: cpputest.id, + message: output, + state: 'passed', + description: '(10ms)', + tooltip: 'Name: TestCase.TestName\nā±Duration: 10ms', + decorations: [], + }; + + assert.strictEqual(cpputest.description, ev.description); + assert.strictEqual(cpputest.tooltip, ev.tooltip); + assert.strictEqual(cpputest.lastRunMilisec, 10); + assert.deepStrictEqual(ev, expected); + assert.deepStrictEqual(cpputest.lastRunEvent, expected); + }); + + it('parses ignored test', function () { + const output = ['IGNORE_TEST(UtestShell, IgnoreTestAccessingFixture) - 0 ms'].join(EOL); + + const ev = cpputest.parseAndProcessTestCase('runid', output, 0, null, ''); + + const expected: TestRunEvent = { + testRunId: 'runid', + type: 'test', + test: cpputest.id, + message: output, + state: 'skipped', + description: '(0ms)', + tooltip: 'Name: TestCase.TestName\nā±Duration: 0ms', + decorations: [], + }; + + assert.strictEqual(cpputest.description, ev.description); + assert.strictEqual(cpputest.tooltip, ev.tooltip); + assert.strictEqual(cpputest.lastRunMilisec, 0); + assert.deepStrictEqual(ev, expected); + assert.deepStrictEqual(cpputest.lastRunEvent, expected); + }); +});