Create code-coverage plugin

In order to use this plugin, you must set the
`backstage.io/code-coverage` annotation on your entity.

```yaml
backstage.io/code-coverage: enabled
```

There's a feature to only include files that are in SCM in the coverage
report, this is helpful to not count generated files for example. To
enable this set the `backstage.io/code-coverage` annotation to
`scm-only`.

```yaml
backstage.io/code-coverage: scm-only
```

The backend plugin provides API endpoints for submitting code-coverage
reports. Currently jacoco and cobertura are supported. These reports
are normalized to a json format that is stored in the database.

```json
// curl -X POST -H "Content-Type:text/xml" -d @cobertura.xml "localhost:7000/api/code-coverage/Component/default/entity-name?coverageType=cobertura"
{
    "links": [
        {
            "href": "http://localhost:7000/api/code-coverage/Component/default/entity-name",
            "rel": "coverage"
        }
    ]
}
```

It also provides some additional API endpoints:
* Viewing the latest report
* Viewing a more condensed history of code coverage values
* Retrieving file contents from source-control, used by the UI

Provides a graph of code coverage change over time, as well as a file
view where you can see the highlighted lines.

Co-authored-by: nissayeva <natashaaay@gmail.com>
Signed-off-by: alde <r.dybeck@gmail.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
alde
2021-02-24 16:48:38 -05:00
committed by Fredrik Adelöw
parent ce05bf61e1
commit e7d2fb93f0
67 changed files with 16733 additions and 11 deletions
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
};
+150
View File
@@ -0,0 +1,150 @@
# code-coverage
This is the backend part of the code-coverage plugin. It takes care of processing various coverage formats and standardizing them into a single json format, used by the frontend.
## Configuring your entity
In order to use this plugin, you must set the `backstage.io/code-coverage` annotation.
```yaml
backstage.io/code-coverage: enabled
```
There's a feature to only include files that are in VCS in the coverage report, this is helpful to not count generated files for example. To enable this set the `backstage.io/code-coverage` annotation to `scm-only`.
```yaml
backstage.io/code-coverage: scm-only
```
Note: This requires the [`backstage.io/source-location` annotation](https://backstage.io/docs/features/software-catalog/well-known-annotations#backstageiosource-location) to be set.
## API
### Adding cobertura report
POST a cobertura xml to `/:kind/:namespace/:name?coverageType=cobertura`
Example:
```json
// curl -X POST -H "Content-Type:text/xml" -d @cobertura.xml "localhost:7000/api/code-coverage/Component/default/entity-name?coverageType=cobertura"
{
"links": [
{
"href": "http://localhost:7000/api/code-coverage/Component/default/entity-name",
"rel": "coverage"
}
]
}
```
### Adding jacoco report
POST a jacoco xml to `/:kind/:namespace/:name?coverageType=jacoco`
Example:
```json
// curl -X POST -H "Content-Type:text/xml" -d @jacoco.xml "localhost:7000/api/code-coverage/Component/default/entity-name?coverageType=jacoco"
{
"links": [
{
"href": "http://localhost:7000/api/code-coverage/Component/default/entity-name",
"rel": "coverage"
}
]
}
```
### Reading json coverage
GET `/:kind/:namespace/:name`
Example:
```json
// curl localhost:7000/api/code-coverage/Component/default/entity-name
{
"aggregate": {
"branch": {
"available": 0,
"covered": 0,
"missed": 0,
"percentage": 0
},
"line": {
"available": 5,
"covered": 4,
"missed": 1,
"percentage": 80
}
},
"entity": {
"kind": "Component",
"name": "entity-name",
"namespace": "default"
},
"files": [
{
"branchHits": {},
"filename": "main.go",
"lineHits": {
"117": 12,
"142": 8,
"34": 8,
"42": 0,
"58": 6
}
}
]
}
```
### Coverage history
GET `/:kind/:namespace/:name/history`
Example
```json
// curl localhost:7000/api/code-coverage/Component/default/entity-name/history
{
"entity": {
"kind": "Component",
"name": "entity-name",
"namespace": "default"
},
"history": [
{
"branch": {
"available": 0,
"covered": 0,
"missed": 0,
"percentage": 0
},
"line": {
"available": 299,
"covered": 116,
"missed": 183,
"percentage": 38.8
},
"timestamp": 1615490766141
},
{
"branch": {
"available": 0,
"covered": 0,
"missed": 0,
"percentage": 0
},
"line": {
"available": 299,
"covered": 116,
"missed": 183,
"percentage": 38.8
},
"timestamp": 1615406307929
}
]
}
```
@@ -0,0 +1,50 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @ts-check
/**
* @param {import('knex').Knex} knex
*/
exports.up = async function up(knex) {
await knex.schema.createTable('code_coverage', table => {
table.comment('The table of code coverage');
table
.uuid('id')
.primary()
.notNullable()
.comment('The ID of the code coverage');
table.text('entity_name').notNullable().comment('entity name');
table.text('entity_kind').notNullable().comment('entity kind');
table.text('entity_namespace').notNullable().comment('entity namespace');
table
.text('coverage')
.notNullable()
.comment('The coverage json as a string');
table
.dateTime('created_at')
.defaultTo(knex.fn.now())
.notNullable()
.comment('The timestamp when this entry was created');
});
};
/**
* @param {import('knex').Knex} knex
*/
exports.down = async function down(knex) {
await knex.schema.dropTable('code_coverage');
};
@@ -0,0 +1,50 @@
{
"name": "@backstage/plugin-code-coverage-backend",
"version": "0.1.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"scripts": {
"start": "backstage-cli backend:dev",
"build": "backstage-cli backend:build",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"prepack": "backstage-cli prepack",
"postpack": "backstage-cli postpack",
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.5.4",
"@backstage/catalog-client": "^0.3.6",
"@backstage/catalog-model": "^0.7.2",
"@backstage/config": "^0.1.3",
"@backstage/errors": "^0.1.1",
"@backstage/integration": "^0.5.0",
"@backstage/plugin-catalog-backend": "^0.6.5",
"@types/express": "^4.17.6",
"cross-fetch": "^3.0.6",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
"express-xml-bodyparser": "^0.3.0",
"knex": "^0.95.1",
"uuid": "^8.3.2",
"winston": "^3.2.1",
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.6.2",
"@types/express-xml-bodyparser": "^0.3.2",
"@types/supertest": "^2.0.8",
"msw": "^0.21.2",
"supertest": "^4.0.2",
"xml2js": "^0.4.23"
},
"files": [
"dist"
]
}
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './service/router';
+33
View File
@@ -0,0 +1,33 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { getRootLogger } from '@backstage/backend-common';
import yn from 'yn';
import { startStandaloneServer } from './service/standaloneServer';
const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000;
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
const logger = getRootLogger();
startStandaloneServer({ port, enableCors, logger }).catch(err => {
logger.error(err);
process.exit(1);
});
process.on('SIGINT', () => {
logger.info('CTRL+C pressed; exiting.');
process.exit(0);
});
@@ -0,0 +1,199 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
getVoidLogger,
SingleConnectionDatabaseManager,
} from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import {
CodeCoverageDatabase,
CodeCoverageStore,
} from './CodeCoverageDatabase';
import { JsonCodeCoverage } from './jsoncoverage-types';
const db = SingleConnectionDatabaseManager.fromConfig(
new ConfigReader({
backend: {
database: {
client: 'sqlite3',
connection: ':memory:',
},
},
}),
).forPlugin('code-coverage');
const coverage: Array<JsonCodeCoverage> = [
{
metadata: {
generationTime: 1234567890,
vcs: {
location: 'local',
type: 'local',
},
},
entity: {
kind: 'Component',
name: 'test-entity',
namespace: 'default',
},
files: [
{
filename: 'src/main.py',
lineHits: {
'10': 5,
'11': 4,
'12': 4,
},
branchHits: {},
},
],
},
{
metadata: {
generationTime: 2345678901,
vcs: {
location: 'local',
type: 'local',
},
},
entity: {
kind: 'Component',
name: 'test-entity',
namespace: 'default',
},
files: [
{
filename: 'src/main.py',
lineHits: {
'10': 5,
'11': 4,
'12': 4,
'22': 0,
'23': 0,
'24': 0,
'30': 1,
},
branchHits: {
'10': {
available: 2,
covered: 1,
missed: 1,
},
},
},
],
},
];
let database: CodeCoverageStore;
describe('CodeCoverageDatabase', () => {
beforeAll(async () => {
const client = await db.getClient();
database = await CodeCoverageDatabase.create(client, getVoidLogger());
database.insertCodeCoverage(coverage[0]);
await new Promise(r => setTimeout(r, 1000));
database.insertCodeCoverage(coverage[1]);
});
describe('insertCodeCoverage', () => {
it('can insert code coverage', async () => {
const ncov = {
metadata: {
generationTime: 3456789012,
vcs: {
location: 'local',
type: 'local',
},
},
entity: {
kind: 'Component',
name: 'test-entity-for-insert',
namespace: 'default',
},
files: [
{
filename: 'src/main.py',
lineHits: {
'10': 5,
'11': 4,
'12': 4,
},
branchHits: {},
},
],
};
const { codeCoverageId } = await database.insertCodeCoverage(ncov);
expect(codeCoverageId.length).not.toBe(0);
});
});
describe('getCodeCoverage', () => {
it("can get coverage that's in the database", async () => {
const cov = await database.getCodeCoverage({
name: 'test-entity',
kind: 'Component',
namespace: 'default',
});
expect(cov).toEqual(coverage[1]);
});
});
describe('getHistory', () => {
it("can get history that's in the database", async () => {
const cov = await database.getHistory(
{
name: 'test-entity',
kind: 'Component',
namespace: 'default',
},
5,
);
expect(cov.history.length).toEqual(2);
expect(cov.history).toEqual([
{
branch: {
available: 2,
covered: 1,
missed: 1,
percentage: 50,
},
line: {
available: 7,
covered: 4,
missed: 3,
percentage: 57.14,
},
timestamp: 2345678901,
},
{
branch: {
available: 0,
covered: 0,
missed: 0,
percentage: 0,
},
line: {
available: 3,
covered: 3,
missed: 0,
percentage: 100,
},
timestamp: 1234567890,
},
]);
});
});
});
@@ -0,0 +1,112 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { resolvePackagePath } from '@backstage/backend-common';
import { NotFoundError } from '@backstage/errors';
import { EntityName } from '@backstage/catalog-model';
import { Knex } from 'knex';
import { v4 as uuid } from 'uuid';
import { Logger } from 'winston';
import { aggregateCoverage } from './CoverageUtils';
import { JsonCodeCoverage, JsonCoverageHistory } from './jsoncoverage-types';
export type RawDbCoverageRow = {
id: string;
entity_name: string;
entity_namespace: string;
entity_kind: string;
coverage: string;
};
export interface CodeCoverageStore {
insertCodeCoverage(
coverage: JsonCodeCoverage,
): Promise<{ codeCoverageId: string }>;
getCodeCoverage(entity: EntityName): Promise<JsonCodeCoverage>;
getHistory(entity: EntityName, limit: number): Promise<JsonCoverageHistory>;
}
const migrationsDir = resolvePackagePath(
'@backstage/plugin-code-coverage-backend',
'migrations',
);
export class CodeCoverageDatabase implements CodeCoverageStore {
static async create(knex: Knex, logger: Logger): Promise<CodeCoverageStore> {
await knex.migrate.latest({
directory: migrationsDir,
});
return new CodeCoverageDatabase(knex, logger);
}
constructor(private readonly db: Knex, private readonly logger: Logger) {}
async insertCodeCoverage(
coverage: JsonCodeCoverage,
): Promise<{ codeCoverageId: string }> {
const codeCoverageId = uuid();
await this.db<RawDbCoverageRow>('code_coverage').insert({
id: codeCoverageId,
entity_name: coverage.entity.name,
entity_kind: coverage.entity.kind,
entity_namespace: coverage.entity.namespace,
coverage: JSON.stringify(coverage),
});
return { codeCoverageId };
}
async getCodeCoverage(entity: EntityName): Promise<JsonCodeCoverage> {
const [result] = await this.db<RawDbCoverageRow>('code_coverage')
.where({
entity_name: entity.name,
entity_kind: entity.kind,
entity_namespace: entity.namespace,
})
.orderBy('created_at', 'desc')
.limit(1)
.select();
if (!result) {
throw new NotFoundError(
`No coverage for entity '${JSON.stringify(entity)}' found`,
);
}
try {
return JSON.parse(result.coverage);
} catch (error) {
throw new Error(`Failed to parse coverage for '${entity}', ${error}`);
}
}
async getHistory(
entity: EntityName,
limit: number,
): Promise<JsonCoverageHistory> {
const res = await this.db<RawDbCoverageRow>('code_coverage')
.where({
entity_name: entity.name,
entity_kind: entity.kind,
entity_namespace: entity.namespace,
})
.orderBy('created_at', 'desc')
.limit(limit)
.select();
const history = res
.map(r => JSON.parse(r.coverage))
.map(c => aggregateCoverage(c));
return {
entity,
history: history,
};
}
}
@@ -0,0 +1,325 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fs from 'fs';
import { Readable } from 'stream';
import {
calculatePercentage,
aggregateCoverage,
CoverageUtils,
} from './CoverageUtils';
import { Entity } from '@backstage/catalog-model';
import { parseString } from 'xml2js';
describe('calculatePercentage', () => {
[
[100, 25, 25],
[100, 100, 100],
[133, 13, 9.77],
[0, 0, 0],
].forEach(([a, c, e]) => {
it(`${c}/${a} === ${e}`, () => {
expect(calculatePercentage(a, c)).toEqual(e);
});
});
});
/* eslint-disable no-restricted-syntax */
describe('aggregateCoverage', () => {
[
{
file: 'jacoco-jsoncoverage-files-1.json',
expected: {
branch: {
available: 68,
covered: 48,
missed: 20,
percentage: 70.59,
},
line: {
available: 162,
covered: 105,
missed: 57,
percentage: 64.81,
},
timestamp: 1234567890,
},
},
{
file: 'cobertura-jsoncoverage-files-1.json',
expected: {
branch: {
available: 0,
covered: 0,
missed: 0,
percentage: 0,
},
line: {
available: 146,
covered: 145,
missed: 1,
percentage: 99.32,
},
timestamp: 1234567890,
},
},
{
file: 'cobertura-jsoncoverage-files-2.json',
expected: {
branch: {
available: 466,
covered: 380,
missed: 86,
percentage: 81.55,
},
line: {
available: 2632,
covered: 2079,
missed: 553,
percentage: 78.99,
},
timestamp: 1234567890,
},
},
{
file: 'cobertura-jsoncoverage-files-3.json',
expected: {
branch: {
available: 73,
covered: 49,
missed: 24,
percentage: 67.12,
},
line: {
available: 110,
covered: 91,
missed: 19,
percentage: 82.73,
},
timestamp: 1234567890,
},
},
{
file: 'cobertura-jsoncoverage-files-4.json',
expected: {
branch: {
available: 0,
covered: 0,
missed: 0,
percentage: 0,
},
line: {
available: 325,
covered: 0,
missed: 325,
percentage: 0,
},
timestamp: 1234567890,
},
},
{
file: 'cobertura-jsoncoverage-files-5.json',
expected: {
branch: {
available: 38,
covered: 21,
missed: 17,
percentage: 55.26,
},
line: {
available: 175,
covered: 124,
missed: 51,
percentage: 70.86,
},
timestamp: 1234567890,
},
},
].forEach(td => {
it(`processes ${td.file}`, () => {
const json = JSON.parse(
fs.readFileSync(`${__dirname}/__fixtures__/${td.file}`).toString(),
);
const aggregate = aggregateCoverage({
metadata: {
generationTime: 1234567890,
vcs: {
location: 'foo',
type: 'foo',
},
},
entity: {
kind: 'foo',
name: 'foo',
namespace: 'default',
},
files: json,
});
expect(aggregate).toEqual(td.expected);
});
});
});
describe('CodeCoverageUtils', () => {
const scmFilesFixture = fs
.readFileSync(`${__dirname}/__fixtures__/cobertura-sourcefiles-1.txt`)
.toString()
.split('\n')
.map(f => {
return { path: f };
});
const scmIntegraions = {
byUrl: jest.fn().mockReturnValue({
type: 'local',
title: 'local',
}),
};
const scmTree = {
files: jest
.fn()
.mockReturnValue(new Promise((r, _e) => r(scmFilesFixture))),
};
const urlReader = {
readTree: jest.fn().mockReturnValue(new Promise((r, _e) => r(scmTree))),
};
const utils = new CoverageUtils(scmIntegraions, urlReader);
describe('validateRequestBody', () => {
it('rejects missing content type', () => {
let err: Error | null = null;
try {
const mockRequest: Partial<Request> = {
// @ts-ignore
headers: {},
};
// @ts-ignore
utils.validateRequestBody(mockRequest as Request);
} catch (error) {
err = error;
}
expect(err?.message).toEqual('Content-Type missing');
});
it('rejects unsupported content type', () => {
let err: Error | null = null;
try {
const mockRequest: Partial<Request> = {
headers: {
// @ts-ignore
'content-type': 'application/json',
},
};
// @ts-ignore
utils.validateRequestBody(mockRequest as Request);
} catch (error) {
err = error;
}
expect(err?.message).toEqual('Illegal Content-Type');
});
it('parses the body', () => {
const mockRequest: Partial<Request> = {
headers: {
// @ts-ignore
'content-type': 'text/xml',
},
// @ts-ignore
body: Readable.from(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?><report name="example"></report>',
),
};
// @ts-ignore
const data: Readable = utils.validateRequestBody(mockRequest as Request);
expect(data.read()).toContain('<?xml');
});
});
describe('processCoveragePayload', () => {
const mockRequest: Partial<Request> = {
headers: {
// @ts-ignore
'content-type': 'text/xml',
},
// @ts-ignore
body: Readable.from(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?><report name="example"></report>',
),
};
it('ignores scm if annotation is not set', async () => {
const entity: Entity = {
kind: 'Component',
metadata: {
name: 'test-entity',
namespace: 'test',
},
apiVersion: 'backstage.io/v1alpha1',
};
const {
scmFiles,
sourceLocation,
vcs,
body, // @ts-ignore
} = await utils.processCoveragePayload(entity, mockRequest);
let data;
// in normal flow the express app will already have done this through the middleware
parseString((body as Readable).read(), (_e, r) => {
data = r;
});
expect(scmFiles.length).toBe(0);
expect(vcs).toBeUndefined();
expect(sourceLocation).toBeUndefined();
expect(data).toEqual({
report: {
$: {
name: 'example',
},
},
});
});
it('populates scm data if annotation is set', async () => {
const entity: Entity = {
kind: 'Component',
metadata: {
name: 'test-entity',
namespace: 'test',
annotations: {
'backstage.io/code-coverage': 'scm-only',
'backstage.io/source-location': 'https://github.com/example/test/',
},
},
apiVersion: 'backstage.io/v1alpha1',
};
const {
scmFiles,
sourceLocation,
vcs, // @ts-ignore
} = await utils.processCoveragePayload(entity, mockRequest);
expect(scmFiles.length).toBe(scmFiles.length);
expect(vcs).not.toBeUndefined();
expect(sourceLocation).not.toBeUndefined();
});
});
});
@@ -0,0 +1,168 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Request } from 'express';
import { UrlReader } from '@backstage/backend-common';
import { InputError, NotFoundError } from '@backstage/errors';
import { Entity } from '@backstage/catalog-model';
import { ScmIntegration, ScmIntegrations } from '@backstage/integration';
import {
AggregateCoverage,
FileEntry,
JsonCodeCoverage,
} from './jsoncoverage-types';
export const calculatePercentage = (
available: number,
covered: number,
): number => {
if (available === 0) {
return 0;
}
return parseFloat(((covered / available) * 100).toFixed(2));
};
export const aggregateCoverage = (c: JsonCodeCoverage): AggregateCoverage => {
let availableLine = 0;
let coveredLine = 0;
let availableBranch = 0;
let coveredBranch = 0;
c.files.forEach(f => {
availableLine += Object.keys(f.lineHits).length;
coveredLine += Object.values(f.lineHits).filter(l => l > 0).length;
availableBranch += Object.keys(f.branchHits)
.map(b => parseInt(b, 10))
.map((b: number) => f.branchHits[b].available)
.filter(Boolean)
.reduce((acc, curr) => acc + curr, 0);
coveredBranch += Object.keys(f.branchHits)
.map(b => parseInt(b, 10))
.map((b: number) => f.branchHits[b].covered)
.filter(Boolean)
.reduce((acc, curr) => acc + curr, 0);
});
return {
timestamp: c.metadata.generationTime,
branch: {
available: availableBranch,
covered: coveredBranch,
missed: availableBranch - coveredBranch,
percentage: calculatePercentage(availableBranch, coveredBranch),
},
line: {
available: availableLine,
covered: coveredLine,
missed: availableLine - coveredLine,
percentage: calculatePercentage(availableLine, coveredLine),
},
};
};
export class CoverageUtils {
constructor(
readonly scm: Partial<ScmIntegrations>,
readonly urlReader: Partial<UrlReader>,
) {}
async processCoveragePayload(
entity: Entity,
req: Request,
): Promise<{
sourceLocation?: string;
vcs?: ScmIntegration;
scmFiles: string[];
body: {};
}> {
const enforceScmFiles =
entity.metadata.annotations?.['backstage.io/code-coverage'] ===
'scm-only' || false;
let sourceLocation: string | undefined = undefined;
let vcs: ScmIntegration | undefined = undefined;
let scmFiles: string[] = [];
if (enforceScmFiles) {
sourceLocation =
entity.metadata.annotations?.['backstage.io/source-location'];
if (!sourceLocation) {
throw new InputError(
`No "backstage.io/source-location" annotation on entity ${entity.kind}/${entity.metadata.namespace}/${entity.metadata.name}`,
);
}
vcs = this.scm.byUrl?.(sourceLocation);
if (!vcs) {
throw new InputError(`Unable to determine SCM from ${sourceLocation}`);
}
const scmTree = await this.urlReader.readTree?.(sourceLocation);
if (!scmTree) {
throw new NotFoundError(`Unable to read tree from ${sourceLocation}`);
}
scmFiles = (await scmTree.files()).map(f => f.path);
}
const body = this.validateRequestBody(req);
if (Object.keys(body).length === 0) {
throw new InputError('Unable to parse body');
}
return {
sourceLocation,
vcs,
scmFiles,
body,
};
}
async buildCoverage(
entity: Entity,
sourceLocation: string | undefined,
vcs: ScmIntegration | undefined,
files: FileEntry[],
): Promise<JsonCodeCoverage> {
return {
metadata: {
vcs: {
type: vcs?.type || 'unknown',
location: sourceLocation || 'unknown',
},
generationTime: Date.now(),
},
entity: {
name: entity.metadata.name,
namespace: entity.metadata.namespace || 'default',
kind: entity.kind,
},
files,
};
}
validateRequestBody(req: Request) {
const contentType = req.headers['content-type'];
if (!contentType) {
throw new InputError('Content-Type missing');
} else if (!contentType.match(/^text\/xml($|;)/)) {
throw new InputError('Illegal Content-Type');
}
const body = req.body;
if (!body) {
throw new InputError('Missing request body');
}
return body;
}
}
@@ -0,0 +1,166 @@
[
{
"filename": "src/main/scala/com/example/Main.scala",
"lineHits": {
"100": 4,
"103": 2,
"104": 4,
"105": 4,
"112": 2,
"114": 4,
"115": 2,
"124": 3,
"125": 2,
"126": 3,
"127": 3,
"135": 3,
"136": 1,
"138": 2,
"139": 1,
"140": 1,
"143": 1,
"145": 1,
"146": 1,
"147": 1,
"148": 1,
"149": 1,
"150": 1,
"160": 2,
"163": 8,
"164": 4,
"166": 2,
"167": 4,
"168": 6,
"169": 6,
"170": 4,
"178": 1,
"179": 1,
"181": 1,
"182": 1,
"183": 3,
"187": 1,
"188": 3,
"202": 4,
"203": 6,
"204": 4,
"210": 5,
"26": 4,
"27": 4,
"28": 4,
"30": 4,
"33": 4,
"35": 2,
"36": 2,
"37": 4,
"38": 2,
"42": 2,
"44": 2,
"46": 2,
"48": 2,
"50": 2,
"52": 2,
"53": 2,
"55": 2,
"56": 2,
"58": 2,
"60": 2,
"61": 2,
"64": 2,
"65": 6,
"68": 4,
"73": 2,
"75": 10,
"77": 4,
"83": 1,
"84": 2,
"85": 3,
"86": 3,
"87": 1,
"88": 6,
"95": 2,
"97": 8,
"99": 2
},
"branchHits": {}
},
{
"filename": "src/main/scala/com/example/Other.scala",
"lineHits": {
"103": 6,
"104": 2,
"105": 5,
"109": 1,
"110": 3,
"111": 3,
"113": 2,
"115": 2,
"116": 2,
"117": 2,
"119": 2,
"123": 3,
"124": 1,
"129": 7,
"132": 2,
"133": 4,
"134": 2,
"138": 1,
"139": 2,
"140": 2,
"142": 4,
"143": 3,
"144": 5,
"145": 3,
"146": 5,
"147": 3,
"149": 2,
"20": 1,
"21": 1,
"23": 1,
"25": 2,
"26": 1,
"27": 2,
"29": 2,
"31": 2,
"32": 2,
"33": 2,
"34": 2,
"37": 0,
"42": 4,
"44": 2,
"45": 2,
"46": 2,
"49": 4,
"50": 10,
"52": 4,
"59": 5,
"61": 4,
"64": 2,
"65": 1,
"66": 1,
"68": 1,
"70": 4,
"71": 2,
"72": 2,
"73": 1,
"74": 2,
"79": 1,
"85": 1,
"86": 3,
"87": 1,
"91": 1,
"92": 4,
"93": 3,
"94": 2
},
"branchHits": {}
},
{
"filename": "src/main/scala/com/example/utils/Util.scala",
"lineHits": {
"13": 2,
"8": 4,
"7": 2
},
"branchHits": {}
}
]
@@ -0,0 +1,297 @@
[
{
"filename": "src/atoms/BoxShadow/box-shadow.ts",
"lineHits": {
"4": 5
},
"branchHits": {}
},
{
"filename": "src/atoms/Button/button.tsx",
"lineHits": {
"17": 41,
"34": 41,
"70": 82,
"72": 82,
"74": 41,
"31": 41,
"32": 41,
"35": 0,
"36": 0,
"37": 0,
"38": 0,
"39": 0,
"40": 0,
"41": 0,
"42": 0,
"43": 0,
"47": 41,
"68": 10,
"75": 41
},
"branchHits": {
"35": {
"covered": 0,
"missed": 4,
"available": 4
},
"70": {
"covered": 1,
"missed": 0,
"available": 1
},
"72": {
"covered": 3,
"missed": 0,
"available": 3
},
"75": {
"covered": 2,
"missed": 0,
"available": 2
}
}
},
{
"filename": "src/atoms/Container/components.ts",
"lineHits": {
"5": 70,
"19": 70,
"20": 70,
"23": 70,
"4": 13,
"13": 13
},
"branchHits": {}
},
{
"filename": "src/atoms/Container/container.tsx",
"lineHits": {
"12": 35,
"20": 35
},
"branchHits": {}
},
{
"filename": "src/atoms/DownArrow/down-arrow.tsx",
"lineHits": {
"3": 28,
"4": 28
},
"branchHits": {}
},
{
"filename": "src/molecules/Hero/components.ts",
"lineHits": {
"26": 20,
"28": 10,
"94": 10,
"103": 20,
"121": 10,
"182": 20,
"203": 5,
"218": 5,
"236": 10,
"241": 10,
"249": 8,
"254": 4,
"264": 4,
"277": 8,
"282": 4,
"294": 8,
"299": 6,
"25": 2,
"29": 10,
"37": 3,
"39": 3,
"40": 3,
"42": 3,
"43": 1,
"44": 1,
"45": 0,
"46": 0,
"48": 1,
"52": 0,
"53": 1,
"57": 0,
"59": 1,
"60": 1,
"63": 3,
"95": 10,
"112": 2,
"122": 10,
"125": 9,
"134": 0,
"151": 1,
"170": 2,
"181": 2,
"194": 2,
"204": 5,
"207": 3,
"209": 0,
"211": 1,
"213": 1,
"219": 5,
"222": 3,
"224": 0,
"226": 0,
"228": 1,
"230": 1,
"245": 2,
"255": 4,
"258": 3,
"260": 1,
"265": 4,
"268": 2,
"270": 1,
"272": 1,
"283": 4,
"286": 2,
"288": 1,
"290": 1,
"298": 2,
"304": 2,
"311": 2,
"319": 2,
"329": 2,
"361": 2,
"371": 2
},
"branchHits": {
"29": {
"covered": 2,
"missed": 0,
"available": 2
},
"39": {
"covered": 2,
"missed": 0,
"available": 2
},
"42": {
"covered": 2,
"missed": 0,
"available": 2
},
"43": {
"covered": 3,
"missed": 1,
"available": 4
},
"45": {
"covered": 0,
"missed": 4,
"available": 4
},
"48": {
"covered": 1,
"missed": 1,
"available": 2
},
"53": {
"covered": 1,
"missed": 1,
"available": 2
},
"59": {
"covered": 1,
"missed": 1,
"available": 2
},
"60": {
"covered": 1,
"missed": 1,
"available": 2
},
"95": {
"covered": 1,
"missed": 1,
"available": 2
},
"122": {
"covered": 2,
"missed": 2,
"available": 4
},
"182": {
"covered": 2,
"missed": 0,
"available": 2
},
"204": {
"covered": 4,
"missed": 1,
"available": 5
},
"219": {
"covered": 4,
"missed": 2,
"available": 6
},
"236": {
"covered": 1,
"missed": 0,
"available": 1
},
"241": {
"covered": 1,
"missed": 0,
"available": 1
},
"249": {
"covered": 1,
"missed": 1,
"available": 2
},
"255": {
"covered": 3,
"missed": 0,
"available": 3
},
"265": {
"covered": 4,
"missed": 0,
"available": 4
},
"277": {
"covered": 1,
"missed": 0,
"available": 1
},
"283": {
"covered": 4,
"missed": 0,
"available": 4
},
"294": {
"covered": 1,
"missed": 1,
"available": 2
},
"299": {
"covered": 1,
"missed": 1,
"available": 2
}
}
},
{
"filename": "src/molecules/Hero/hero.tsx",
"lineHits": {
"40": 10,
"72": 0,
"127": 3,
"56": 10,
"58": 10,
"73": 0,
"128": 3
},
"branchHits": {
"73": {
"covered": 0,
"missed": 2,
"available": 2
}
}
}
]
@@ -0,0 +1,351 @@
[
{
"filename": "Sources/TDSOptions.m",
"lineHits": {
"32": 0,
"33": 0,
"34": 0,
"35": 0,
"36": 0,
"37": 0,
"38": 0,
"45": 0,
"46": 0,
"47": 0,
"48": 0,
"49": 0,
"50": 0,
"51": 0,
"52": 0,
"53": 0,
"54": 0,
"55": 0,
"56": 0,
"57": 0,
"58": 0,
"61": 0,
"62": 0,
"63": 0,
"66": 0,
"67": 0,
"68": 0,
"69": 0,
"70": 0,
"71": 0,
"72": 0,
"73": 0,
"74": 0,
"75": 0,
"76": 0,
"77": 0
},
"branchHits": {}
},
{
"filename": "Sources/TDSUserLock.m",
"lineHits": {
"17": 0,
"18": 0,
"19": 0,
"20": 0,
"21": 0,
"22": 0,
"23": 0,
"24": 0,
"25": 0,
"26": 0,
"27": 0,
"28": 0,
"29": 0,
"30": 0,
"31": 0,
"32": 0,
"33": 0,
"34": 0,
"35": 0,
"36": 0,
"37": 0,
"38": 0,
"39": 0,
"40": 0,
"43": 0,
"44": 0,
"45": 0,
"46": 0,
"47": 0,
"48": 0,
"49": 0,
"58": 0,
"59": 0,
"60": 0,
"61": 0,
"62": 0,
"63": 0,
"64": 0,
"65": 0,
"66": 0,
"67": 0,
"68": 0,
"69": 0,
"70": 0
},
"branchHits": {}
},
{
"filename": "Sources/TDSUserManager.m",
"lineHits": {
"24": 0,
"25": 0,
"26": 0,
"27": 0,
"28": 0,
"29": 0,
"30": 0,
"31": 0,
"34": 0,
"35": 0,
"36": 0,
"37": 0,
"38": 0,
"39": 0,
"40": 0,
"49": 0,
"50": 0,
"51": 0,
"52": 0,
"53": 0,
"54": 0,
"55": 0,
"56": 0,
"57": 0,
"58": 0,
"59": 0,
"60": 0,
"61": 0,
"62": 0,
"63": 0,
"64": 0,
"65": 0,
"66": 0,
"67": 0,
"68": 0,
"69": 0,
"70": 0,
"74": 0,
"75": 0,
"76": 0,
"77": 0,
"78": 0,
"79": 0,
"80": 0,
"81": 0,
"82": 0,
"83": 0,
"84": 0,
"85": 0,
"86": 0,
"87": 0,
"88": 0,
"89": 0,
"90": 0,
"91": 0,
"92": 0,
"93": 0,
"94": 0,
"101": 0,
"102": 0,
"103": 0,
"104": 0,
"105": 0,
"106": 0,
"107": 0,
"108": 0,
"109": 0,
"110": 0,
"111": 0,
"112": 0,
"113": 0,
"114": 0,
"115": 0,
"116": 0,
"117": 0,
"118": 0,
"119": 0,
"120": 0,
"121": 0,
"122": 0,
"123": 0,
"124": 0,
"125": 0,
"126": 0,
"127": 0,
"128": 0,
"129": 0,
"130": 0,
"131": 0,
"136": 0,
"137": 0,
"138": 0,
"139": 0,
"140": 0,
"141": 0,
"142": 0,
"143": 0,
"144": 0,
"145": 0,
"146": 0,
"147": 0,
"148": 0,
"149": 0,
"150": 0,
"151": 0,
"152": 0,
"153": 0,
"158": 0,
"159": 0,
"160": 0,
"161": 0,
"162": 0,
"163": 0,
"164": 0,
"165": 0,
"166": 0,
"167": 0,
"168": 0,
"169": 0,
"170": 0,
"171": 0,
"172": 0,
"173": 0,
"174": 0,
"175": 0,
"176": 0,
"177": 0,
"178": 0,
"179": 0,
"180": 0,
"181": 0,
"182": 0,
"183": 0,
"184": 0,
"189": 0,
"190": 0,
"191": 0,
"192": 0,
"193": 0,
"194": 0,
"195": 0,
"196": 0,
"197": 0,
"198": 0,
"199": 0,
"200": 0,
"201": 0,
"202": 0,
"203": 0,
"204": 0,
"205": 0,
"206": 0,
"207": 0,
"208": 0,
"209": 0,
"210": 0,
"211": 0,
"212": 0,
"213": 0,
"214": 0,
"215": 0,
"216": 0,
"217": 0,
"218": 0,
"219": 0,
"220": 0,
"221": 0,
"222": 0,
"223": 0,
"224": 0,
"225": 0,
"226": 0,
"227": 0,
"228": 0,
"229": 0,
"230": 0,
"231": 0,
"232": 0,
"233": 0,
"234": 0,
"235": 0,
"236": 0,
"239": 0,
"240": 0,
"241": 0,
"242": 0,
"243": 0,
"244": 0,
"245": 0,
"246": 0,
"247": 0,
"248": 0,
"249": 0,
"252": 0,
"253": 0,
"254": 0,
"255": 0,
"256": 0,
"257": 0,
"258": 0,
"259": 0,
"260": 0,
"261": 0,
"262": 0,
"263": 0,
"264": 0,
"265": 0,
"268": 0,
"269": 0,
"270": 0,
"271": 0,
"272": 0,
"273": 0,
"274": 0,
"275": 0,
"276": 0,
"277": 0,
"278": 0,
"279": 0,
"280": 0,
"281": 0,
"282": 0,
"283": 0,
"284": 0,
"287": 0,
"288": 0,
"289": 0,
"290": 0,
"291": 0,
"292": 0,
"293": 0,
"294": 0,
"295": 0,
"296": 0
},
"branchHits": {}
},
{
"filename": "Sources/TDSUtil.m",
"lineHits": {
"8": 0,
"9": 0,
"10": 0,
"11": 0,
"12": 0,
"13": 0,
"16": 0,
"17": 0,
"18": 0,
"19": 0,
"20": 0
},
"branchHits": {}
}
]
@@ -0,0 +1,325 @@
[
{
"filename": "src/graphql/queries.ts",
"lineHits": {
"1": 3,
"3": 3,
"50": 3,
"65": 3,
"80": 3,
"92": 3,
"138": 3
},
"branchHits": {}
},
{
"filename": "src/graphql/resolvers.ts",
"lineHits": {
"22": 17,
"28": 14,
"36": 16,
"27": 8,
"35": 8,
"43": 9,
"51": 15,
"56": 8,
"61": 8,
"50": 8,
"55": 8,
"60": 8,
"128": 7,
"139": 4,
"145": 8,
"160": 8,
"177": 7,
"185": 13,
"70": 12,
"78": 8,
"81": 8,
"84": 4,
"97": 12,
"103": 8,
"114": 8,
"144": 8,
"159": 8,
"176": 8,
"184": 8,
"1": 4,
"2": 4,
"3": 4,
"5": 4,
"6": 4,
"7": 4,
"8": 4,
"9": 4,
"10": 4,
"14": 4,
"21": 4,
"24": 17,
"32": 10,
"37": 12,
"42": 4,
"45": 9,
"47": 9,
"52": 11,
"57": 4,
"62": 4,
"67": 4,
"73": 8,
"83": 4,
"85": 4,
"86": 4,
"87": 4,
"88": 4,
"94": 4,
"95": 4,
"98": 4,
"104": 4,
"107": 4,
"115": 4,
"118": 4,
"119": 4,
"120": 0,
"124": 4,
"130": 7,
"132": 7,
"134": 7,
"136": 7,
"140": 4,
"151": 4,
"165": 4,
"166": 4,
"167": 4,
"168": 1,
"169": 4,
"178": 3,
"186": 9,
"187": 9,
"193": 2,
"194": 0
},
"branchHits": {
"86": {
"covered": 2,
"missed": 0,
"available": 2
},
"119": {
"covered": 1,
"missed": 1,
"available": 2
},
"166": {
"covered": 2,
"missed": 0,
"available": 2
},
"167": {
"covered": 2,
"missed": 0,
"available": 2
},
"193": {
"covered": 1,
"missed": 1,
"available": 2
}
}
},
{
"filename": "src/graphql/router.ts",
"lineHits": {
"21": 1,
"22": 2,
"43": 21,
"53": 40,
"12": 3,
"14": 3,
"23": 1,
"26": 0,
"32": 1,
"33": 1,
"34": 1,
"37": 0,
"54": 11,
"55": 0,
"57": 11,
"58": 11,
"63": 11,
"64": 11,
"65": 10,
"66": 10,
"68": 1
},
"branchHits": {
"23": {
"covered": 3,
"missed": 0,
"available": 3
},
"26": {
"covered": 1,
"missed": 1,
"available": 2
},
"32": {
"covered": 1,
"missed": 1,
"available": 2
},
"55": {
"covered": 2,
"missed": 0,
"available": 2
},
"65": {
"covered": 1,
"missed": 1,
"available": 2
}
}
},
{
"filename": "src/graphql/schema.ts",
"lineHits": {
"9": 7,
"1": 3,
"2": 3,
"4": 3,
"5": 3,
"11": 0,
"13": 4,
"15": 4,
"16": 4,
"17": 4,
"19": 4
},
"branchHits": {
"11": {
"covered": 0,
"missed": 1,
"available": 1
}
}
},
{
"filename": "src/graphql/transforms.ts",
"lineHits": {
"9": 10,
"20": 5,
"13": 6,
"14": 2,
"15": 6,
"16": 1,
"17": 6,
"23": 1,
"24": 0,
"25": 1
},
"branchHits": {
"13": {
"covered": 2,
"missed": 0,
"available": 2
},
"15": {
"covered": 2,
"missed": 0,
"available": 2
},
"23": {
"covered": 1,
"missed": 1,
"available": 2
}
}
},
{
"filename": "src/workers/primary-worker.js",
"lineHits": {
"16": 0,
"25": 0,
"49": 0,
"2": 0,
"3": 0,
"4": 0,
"5": 0,
"6": 0,
"8": 0,
"17": 0,
"21": 0,
"22": 0,
"23": 0,
"24": 0,
"27": 0,
"28": 0,
"31": 0,
"33": 0,
"34": 0,
"37": 0,
"38": 0,
"48": 0,
"51": 0,
"53": 0
},
"branchHits": {
"17": {
"covered": 0,
"missed": 1,
"available": 1
},
"21": {
"covered": 0,
"missed": 2,
"available": 2
},
"49": {
"covered": 0,
"missed": 2,
"available": 2
}
}
},
{
"filename": "src/workers/secondary-worker.js",
"lineHits": {
"4": 0,
"13": 0,
"2": 0,
"5": 0,
"9": 0,
"10": 0,
"11": 0,
"12": 0,
"14": 0,
"15": 0,
"16": 0,
"18": 0,
"19": 0,
"22": 0,
"23": 0,
"24": 0,
"25": 0,
"26": 0,
"27": 0,
"29": 0
},
"branchHits": {
"5": {
"covered": 0,
"missed": 1,
"available": 1
},
"9": {
"covered": 0,
"missed": 2,
"available": 2
},
"24": {
"covered": 0,
"missed": 2,
"available": 2
}
}
}
]
@@ -0,0 +1,19 @@
.dockerignore
.editorconfig
.gitattributes
.gitignore
.gitmodules
.idea/codeStyleSettings.xml
.idea/codeStyles/Project.xml
.idea/codeStyles/codeStyleConfig.xml
.idea/inspectionProfiles/Spotify.xml
.idea/inspectionProfiles/profiles_settings.xml
.mention-bot
.ownership-bot
.slack_mentioner
CONTRIBUTING.md
README.md
WORKSPACE
src/main/scala/com/example/Main.scala
src/main/scala/com/example/Other.scala
src/main/scala/com/example/utils/Util.scala
@@ -0,0 +1,49 @@
.dockerignore
.editorconfig
.gitattributes
.gitignore
.gitmodules
CONTRIBUTING.md
README.md
WORKSPACE
src/index.ts
src/utils.ts
src/controller/VideoContextPlayerCoordinator.ts
src/controller/VideoContextPlayerCoordinatorFactory.ts
src/controller/index.ts
src/controller-x/VideoContextPlayerCoordinatorX.ts
src/controller-x/VideoContextPlayerCoordinatorXObserver.ts
src/controller-x/index.ts
src/events/InternalPlaybackObserver.ts
src/events/PlaybackEventsObserver.ts
src/events/TimeObservable.ts
src/events/index.ts
src/player/BetamaxPlayer.ts
src/player/BetamaxPlayerFactory.ts
src/player/PlaybackSession.ts
src/player/PlaybackSessionFactory.ts
src/player/index.ts
src/player/player.ts
src/player/shaka.ts
src/plugins/SPManifestParser.ts
src/plugins/SpotifyVideoMediaExtension.ts
src/plugins/SpotifyVideoUrl.ts
src/plugins/TextParser.js
src/plugins/VttTextParser.js
src/plugins/WidevineDownloader.ts
src/plugins/index.ts
src/plugins/types.ts
src/tracking/DefaultEventsCalculator.ts
src/tracking/EventsTracker.ts
src/tracking/ProductStatesStreamingRulesProvider.ts
src/tracking/VideoPlaybackEvents.ts
src/tracking/index.ts
src/tracking/types.ts
src/tracking/utils.ts
src/tracking/endvideo/EndVideoEventsCalculator.ts
src/tracking/endvideo/EndVideoLoggerFactory.ts
src/tracking/endvideo/EndVideoLoggerTracker.ts
src/tracking/endvideo/EndVideoReasonEnd.ts
src/tracking/endvideo/EndVideoTracker.ts
src/tracking/session/PlaybackSessionTracker.ts
src/tracking/session/VideoPlaybackSessionLogger.ts
@@ -0,0 +1,29 @@
.dockerignore
.editorconfig
.gitattributes
.gitignore
.gitmodules
.idea/codeStyleSettings.xml
.idea/codeStyles/Project.xml
.idea/codeStyles/codeStyleConfig.xml
.idea/inspectionProfiles/Spotify.xml
.idea/inspectionProfiles/profiles_settings.xml
.mention-bot
.ownership-bot
.slack_mentioner
CONTRIBUTING.md
README.md
WORKSPACE
src/index.ts
src/atoms/BoxShadow/box-shadow.ts
src/atoms/BoxShadow/index.ts
src/atoms/Button/button.tsx
src/atoms/Button/index.ts
src/atoms/Container/components.ts
src/atoms/Container/container.tsx
src/atoms/Container/index.ts
src/atoms/DownArrow/down-arrow.tsx
src/atoms/DownArrow/index.ts
src/molecules/Hero/components.ts
src/molecules/Hero/hero.tsx
src/molecules/Hero/index.ts
@@ -0,0 +1,20 @@
.dockerignore
.editorconfig
.gitattributes
.gitignore
.gitmodules
.idea/codeStyleSettings.xml
.idea/codeStyles/Project.xml
.idea/codeStyles/codeStyleConfig.xml
.idea/inspectionProfiles/Spotify.xml
.idea/inspectionProfiles/profiles_settings.xml
.mention-bot
.ownership-bot
.slack_mentioner
CONTRIBUTING.md
README.md
WORKSPACE
Sources/TDSOptions.m
Sources/TDSUserLock.m
Sources/TDSUserManager.m
Sources/TDSUtil.m
@@ -0,0 +1,26 @@
.editorconfig
.eslintignore
.eslintrc.js
.gitignore
.npmrc
.nvmrc
Dockerfile
README.md
babel.config.js
nodemon.json
package.json
prettier.config.js
service-info.yaml
src/graphql/__tests__/resolvers.test.ts
src/graphql/__tests__/transforms.test.ts
src/graphql/queries.ts
src/graphql/resolvers.ts
src/graphql/router.ts
src/graphql/schema.ts
src/graphql/transforms.ts
src/start.ts
src/sysmodel_component.ts
src/workers/primary-worker.js
src/workers/secondary-worker.js
tsconfig.json
yarn.lock
@@ -0,0 +1,479 @@
<?xml version="1.0"?>
<coverage
line-rate="0.98" lines-valid="312" lines-covered="306" branches-valid="12" branches-covered="12" branch-rate="1.00" complexity="0" version="1.0" timestamp="1567499510503">
<packages>
<package name="com.example" line-rate="0.98" branch-rate="1.00" complexity="0">
<classes>
<class
name="com.example.Main" filename="com/example/Main.scala" line-rate="0.97" branch-rate="1.00" complexity="0">
<methods>
<method
name="com.example/Main/methodOne" signature="()V" line-rate="1.00" branch-rate="1.00" complexity="0">
<lines>
<line number="203" hits="1" branch="false"/>
<line number="202" hits="1" branch="false"/>
<line number="204" hits="1" branch="false"/>
<line number="203" hits="1" branch="false"/>
<line number="202" hits="1" branch="false"/>
<line number="204" hits="1" branch="false"/>
<line number="203" hits="1" branch="false"/>
</lines>
</method>
<method
name="com.example/Main/methodTwo" signature="()V" line-rate="1.00" branch-rate="1.00" complexity="0">
<lines>
<line number="164" hits="1" branch="false"/>
<line number="169" hits="1" branch="false"/>
<line number="170" hits="1" branch="false"/>
<line number="167" hits="1" branch="false"/>
<line number="168" hits="1" branch="false"/>
<line number="163" hits="1" branch="false"/>
<line number="163" hits="1" branch="false"/>
<line number="168" hits="1" branch="false"/>
<line number="169" hits="1" branch="false"/>
<line number="163" hits="1" branch="false"/>
<line number="170" hits="1" branch="false"/>
<line number="167" hits="1" branch="false"/>
<line number="166" hits="1" branch="false"/>
<line number="160" hits="1" branch="false"/>
<line number="168" hits="1" branch="false"/>
<line number="163" hits="1" branch="false"/>
<line number="169" hits="1" branch="false"/>
<line number="164" hits="1" branch="false"/>
</lines>
</method>
<method
name="com.example/Main/methodThree" signature="()V" line-rate="1.00" branch-rate="1.00" complexity="0">
<lines>
<line number="97" hits="1" branch="false"/>
<line number="104" hits="1" branch="false"/>
<line number="99" hits="1" branch="false"/>
<line number="95" hits="1" branch="false"/>
<line number="100" hits="1" branch="false"/>
<line number="104" hits="1" branch="false"/>
<line number="97" hits="1" branch="false"/>
<line number="105" hits="1" branch="false"/>
<line number="97" hits="1" branch="false"/>
<line number="97" hits="1" branch="false"/>
<line number="105" hits="1" branch="false"/>
<line number="100" hits="1" branch="false"/>
<line number="103" hits="1" branch="false"/>
</lines>
</method>
<method
name="com.example/Main/&lt;none&gt;" signature="()V" line-rate="1.00" branch-rate="1.00" complexity="0">
<lines>
<line number="30" hits="1" branch="false"/>
<line number="26" hits="1" branch="false"/>
<line number="28" hits="1" branch="false"/>
<line number="27" hits="1" branch="false"/>
<line number="30" hits="1" branch="false"/>
<line number="26" hits="1" branch="false"/>
<line number="27" hits="1" branch="false"/>
<line number="28" hits="1" branch="false"/>
</lines>
</method>
<method
name="com.example/Main/main" signature="()V" line-rate="1.00" branch-rate="1.00" complexity="0">
<lines>
<line number="56" hits="1" branch="false"/>
<line number="73" hits="1" branch="false"/>
<line number="77" hits="1" branch="false"/>
<line number="75" hits="1" branch="false"/>
<line number="44" hits="1" branch="false"/>
<line number="60" hits="1" branch="false"/>
<line number="35" hits="1" branch="false"/>
<line number="75" hits="1" branch="false"/>
<line number="65" hits="1" branch="false"/>
<line number="50" hits="1" branch="false"/>
<line number="37" hits="1" branch="false"/>
<line number="77" hits="1" branch="false"/>
<line number="37" hits="1" branch="false"/>
<line number="52" hits="1" branch="false"/>
<line number="65" hits="1" branch="false"/>
<line number="42" hits="1" branch="false"/>
<line number="58" hits="1" branch="false"/>
<line number="68" hits="1" branch="false"/>
<line number="33" hits="1" branch="false"/>
<line number="75" hits="1" branch="false"/>
<line number="48" hits="1" branch="false"/>
<line number="64" hits="1" branch="false"/>
<line number="61" hits="1" branch="false"/>
<line number="75" hits="1" branch="false"/>
<line number="33" hits="1" branch="false"/>
<line number="46" hits="1" branch="false"/>
<line number="53" hits="1" branch="false"/>
<line number="36" hits="1" branch="false"/>
<line number="65" hits="1" branch="false"/>
<line number="75" hits="1" branch="false"/>
<line number="38" hits="1" branch="false"/>
<line number="55" hits="1" branch="false"/>
<line number="68" hits="1" branch="false"/>
</lines>
</method>
</methods>
<lines>
<line number="203" hits="1" branch="false"/>
<line number="135" hits="1" branch="false"/>
<line number="149" hits="1" branch="false"/>
<line number="126" hits="1" branch="false"/>
<line number="164" hits="1" branch="false"/>
<line number="169" hits="1" branch="false"/>
<line number="183" hits="1" branch="false"/>
<line number="56" hits="1" branch="false"/>
<line number="138" hits="1" branch="false"/>
<line number="143" hits="1" branch="false"/>
<line number="188" hits="1" branch="false"/>
<line number="170" hits="1" branch="false"/>
<line number="112" hits="1" branch="false"/>
<line number="127" hits="1" branch="false"/>
<line number="86" hits="1" branch="false"/>
<line number="97" hits="1" branch="false"/>
<line number="104" hits="1" branch="false"/>
<line number="73" hits="1" branch="false"/>
<line number="77" hits="1" branch="false"/>
<line number="167" hits="1" branch="false"/>
<line number="210" hits="1" branch="false"/>
<line number="88" hits="1" branch="false"/>
<line number="99" hits="1" branch="false"/>
<line number="75" hits="1" branch="false"/>
<line number="30" hits="1" branch="false"/>
<line number="84" hits="1" branch="false"/>
<line number="44" hits="1" branch="false"/>
<line number="60" hits="1" branch="false"/>
<line number="182" hits="1" branch="false"/>
<line number="124" hits="1" branch="false"/>
<line number="125" hits="1" branch="false"/>
<line number="168" hits="1" branch="false"/>
<line number="146" hits="1" branch="false"/>
<line number="114" hits="1" branch="false"/>
<line number="163" hits="1" branch="false"/>
<line number="35" hits="1" branch="false"/>
<line number="114" hits="1" branch="false"/>
<line number="88" hits="1" branch="false"/>
<line number="75" hits="1" branch="false"/>
<line number="65" hits="1" branch="false"/>
<line number="85" hits="1" branch="false"/>
<line number="50" hits="1" branch="false"/>
<line number="26" hits="1" branch="false"/>
<line number="95" hits="1" branch="false"/>
<line number="28" hits="1" branch="false"/>
<line number="37" hits="1" branch="false"/>
<line number="202" hits="1" branch="false"/>
<line number="210" hits="1" branch="false"/>
<line number="181" hits="1" branch="false"/>
<line number="163" hits="1" branch="false"/>
<line number="168" hits="1" branch="false"/>
<line number="85" hits="1" branch="false"/>
<line number="169" hits="1" branch="false"/>
<line number="183" hits="1" branch="false"/>
<line number="148" hits="1" branch="false"/>
<line number="126" hits="1" branch="false"/>
<line number="163" hits="1" branch="false"/>
<line number="135" hits="1" branch="false"/>
<line number="100" hits="1" branch="false"/>
<line number="88" hits="1" branch="false"/>
<line number="114" hits="1" branch="false"/>
<line number="204" hits="1" branch="false"/>
<line number="127" hits="1" branch="false"/>
<line number="140" hits="1" branch="false"/>
<line number="104" hits="1" branch="false"/>
<line number="97" hits="1" branch="false"/>
<line number="77" hits="1" branch="false"/>
<line number="86" hits="1" branch="false"/>
<line number="188" hits="1" branch="false"/>
<line number="27" hits="1" branch="false"/>
<line number="37" hits="1" branch="false"/>
<line number="170" hits="1" branch="false"/>
<line number="210" hits="1" branch="false"/>
<line number="52" hits="1" branch="false"/>
<line number="65" hits="1" branch="false"/>
<line number="115" hits="1" branch="false"/>
<line number="145" hits="1" branch="false"/>
<line number="167" hits="1" branch="false"/>
<line number="42" hits="1" branch="false"/>
<line number="136" hits="1" branch="false"/>
<line number="30" hits="1" branch="false"/>
<line number="124" hits="1" branch="false"/>
<line number="125" hits="1" branch="false"/>
<line number="105" hits="1" branch="false"/>
<line number="88" hits="1" branch="false"/>
<line number="84" hits="1" branch="false"/>
<line number="58" hits="1" branch="false"/>
<line number="68" hits="1" branch="false"/>
<line number="97" hits="1" branch="false"/>
<line number="33" hits="1" branch="false"/>
<line number="75" hits="1" branch="false"/>
<line number="26" hits="1" branch="false"/>
<line number="48" hits="1" branch="false"/>
<line number="64" hits="1" branch="false"/>
<line number="203" hits="1" branch="false"/>
<line number="187" hits="1" branch="false"/>
<line number="179" hits="1" branch="false"/>
<line number="166" hits="1" branch="false"/>
<line number="97" hits="1" branch="false"/>
<line number="127" hits="1" branch="false"/>
<line number="138" hits="1" branch="false"/>
<line number="210" hits="1" branch="false"/>
<line number="188" hits="1" branch="false"/>
<line number="202" hits="1" branch="false"/>
<line number="160" hits="1" branch="false"/>
<line number="168" hits="1" branch="false"/>
<line number="178" hits="1" branch="false"/>
<line number="105" hits="1" branch="false"/>
<line number="112" hits="1" branch="false"/>
<line number="135" hits="1" branch="false"/>
<line number="147" hits="1" branch="false"/>
<line number="100" hits="1" branch="false"/>
<line number="85" hits="1" branch="false"/>
<line number="88" hits="1" branch="false"/>
<line number="114" hits="1" branch="false"/>
<line number="204" hits="1" branch="false"/>
<line number="124" hits="1" branch="false"/>
<line number="183" hits="1" branch="false"/>
<line number="61" hits="1" branch="false"/>
<line number="75" hits="1" branch="false"/>
<line number="163" hits="1" branch="false"/>
<line number="169" hits="1" branch="false"/>
<line number="33" hits="1" branch="false"/>
<line number="46" hits="1" branch="false"/>
<line number="86" hits="1" branch="false"/>
<line number="53" hits="1" branch="false"/>
<line number="88" hits="1" branch="false"/>
<line number="27" hits="1" branch="false"/>
<line number="36" hits="1" branch="false"/>
<line number="126" hits="1" branch="false"/>
<line number="139" hits="1" branch="false"/>
<line number="150" hits="1" branch="false"/>
<line number="115" hits="1" branch="false"/>
<line number="65" hits="1" branch="false"/>
<line number="75" hits="1" branch="false"/>
<line number="164" hits="1" branch="false"/>
<line number="103" hits="1" branch="false"/>
<line number="38" hits="1" branch="false"/>
<line number="83" hits="1" branch="false"/>
<line number="87" hits="1" branch="false"/>
<line number="55" hits="1" branch="false"/>
<line number="68" hits="1" branch="false"/>
<line number="28" hits="1" branch="false"/>
<line number="210" hits="1" branch="false"/>
<line number="203" hits="1" branch="false"/>
</lines>
</class>
<class
name="com.example.Other" filename="com/example/Other.scala" line-rate="0.99" branch-rate="1.00" complexity="0">
<methods>
<method
name="com.example/Other/methodOne" signature="()V" line-rate="1.00" branch-rate="1.00" complexity="0">
<lines>
<line number="42" hits="1" branch="false"/>
<line number="50" hits="1" branch="false"/>
<line number="46" hits="1" branch="false"/>
<line number="52" hits="1" branch="false"/>
<line number="50" hits="1" branch="false"/>
<line number="44" hits="1" branch="false"/>
<line number="49" hits="1" branch="false"/>
<line number="50" hits="1" branch="false"/>
<line number="50" hits="1" branch="false"/>
<line number="42" hits="1" branch="false"/>
<line number="50" hits="1" branch="false"/>
<line number="52" hits="1" branch="false"/>
<line number="45" hits="1" branch="false"/>
<line number="49" hits="1" branch="false"/>
</lines>
</method>
<method
name="com.example/Other/methodTwo" signature="()V" line-rate="1.00" branch-rate="1.00" complexity="0">
<lines>
<line number="133" hits="1" branch="false"/>
<line number="133" hits="1" branch="false"/>
<line number="134" hits="1" branch="false"/>
<line number="132" hits="1" branch="false"/>
</lines>
</method>
</methods>
<lines>
<line number="91" hits="1" branch="false"/>
<line number="93" hits="1" branch="false"/>
<line number="68" hits="1" branch="false"/>
<line number="42" hits="1" branch="false"/>
<line number="79" hits="1" branch="false"/>
<line number="50" hits="1" branch="false"/>
<line number="117" hits="1" branch="false"/>
<line number="59" hits="1" branch="false"/>
<line number="21" hits="1" branch="false"/>
<line number="31" hits="1" branch="false"/>
<line number="111" hits="1" branch="false"/>
<line number="105" hits="1" branch="false"/>
<line number="61" hits="1" branch="false"/>
<line number="46" hits="1" branch="false"/>
<line number="25" hits="1" branch="false"/>
<line number="52" hits="1" branch="false"/>
<line number="32" hits="1" branch="false"/>
<line number="144" hits="1" branch="false"/>
<line number="146" hits="1" branch="false"/>
<line number="129" hits="1" branch="false"/>
<line number="147" hits="1" branch="false"/>
<line number="109" hits="1" branch="false"/>
<line number="93" hits="1" branch="false"/>
<line number="117" hits="1" branch="false"/>
<line number="104" hits="1" branch="false"/>
<line number="145" hits="1" branch="false"/>
<line number="133" hits="1" branch="false"/>
<line number="142" hits="1" branch="false"/>
<line number="50" hits="1" branch="false"/>
<line number="61" hits="1" branch="false"/>
<line number="129" hits="1" branch="false"/>
<line number="146" hits="1" branch="false"/>
<line number="44" hits="1" branch="false"/>
<line number="119" hits="1" branch="false"/>
<line number="74" hits="1" branch="false"/>
<line number="143" hits="1" branch="false"/>
<line number="146" hits="1" branch="false"/>
<line number="32" hits="1" branch="false"/>
<line number="111" hits="1" branch="false"/>
<line number="139" hits="1" branch="false"/>
<line number="92" hits="1" branch="false"/>
<line number="70" hits="1" branch="false"/>
<line number="103" hits="1" branch="false"/>
<line number="105" hits="1" branch="false"/>
<line number="140" hits="1" branch="false"/>
<line number="115" hits="1" branch="false"/>
<line number="93" hits="1" branch="false"/>
<line number="124" hits="1" branch="false"/>
<line number="103" hits="1" branch="false"/>
<line number="71" hits="1" branch="false"/>
<line number="86" hits="1" branch="false"/>
<line number="27" hits="1" branch="false"/>
<line number="33" hits="1" branch="false"/>
<line number="49" hits="1" branch="false"/>
<line number="59" hits="1" branch="false"/>
<line number="64" hits="1" branch="false"/>
<line number="129" hits="1" branch="false"/>
<line number="110" hits="1" branch="false"/>
<line number="50" hits="1" branch="false"/>
<line number="29" hits="1" branch="false"/>
<line number="37" hits="0" branch="false"/>
<line number="66" hits="1" branch="false"/>
<line number="72" hits="1" branch="false"/>
<line number="87" hits="1" branch="false"/>
<line number="59" hits="1" branch="false"/>
<line number="20" hits="1" branch="false"/>
<line number="149" hits="1" branch="false"/>
<line number="25" hits="1" branch="false"/>
<line number="146" hits="1" branch="false"/>
<line number="140" hits="1" branch="false"/>
<line number="113" hits="1" branch="false"/>
<line number="144" hits="1" branch="false"/>
<line number="123" hits="1" branch="false"/>
<line number="129" hits="1" branch="false"/>
<line number="105" hits="1" branch="false"/>
<line number="59" hits="1" branch="false"/>
<line number="94" hits="1" branch="false"/>
<line number="50" hits="1" branch="false"/>
<line number="72" hits="1" branch="false"/>
<line number="85" hits="1" branch="false"/>
<line number="144" hits="1" branch="false"/>
<line number="147" hits="1" branch="false"/>
<line number="65" hits="1" branch="false"/>
<line number="133" hits="1" branch="false"/>
<line number="142" hits="1" branch="false"/>
<line number="116" hits="1" branch="false"/>
<line number="104" hits="1" branch="false"/>
<line number="123" hits="1" branch="false"/>
<line number="110" hits="1" branch="false"/>
<line number="143" hits="1" branch="false"/>
<line number="42" hits="1" branch="false"/>
<line number="50" hits="1" branch="false"/>
<line number="23" hits="1" branch="false"/>
<line number="103" hits="1" branch="false"/>
<line number="119" hits="1" branch="false"/>
<line number="129" hits="1" branch="false"/>
<line number="74" hits="1" branch="false"/>
<line number="92" hits="1" branch="false"/>
<line number="145" hits="1" branch="false"/>
<line number="31" hits="1" branch="false"/>
<line number="105" hits="1" branch="false"/>
<line number="111" hits="1" branch="false"/>
<line number="61" hits="1" branch="false"/>
<line number="70" hits="1" branch="false"/>
<line number="139" hits="1" branch="false"/>
<line number="52" hits="1" branch="false"/>
<line number="86" hits="1" branch="false"/>
<line number="92" hits="1" branch="false"/>
<line number="33" hits="1" branch="false"/>
<line number="45" hits="1" branch="false"/>
<line number="26" hits="1" branch="false"/>
<line number="61" hits="1" branch="false"/>
<line number="70" hits="1" branch="false"/>
<line number="103" hits="1" branch="false"/>
<line number="34" hits="1" branch="false"/>
<line number="29" hits="1" branch="false"/>
<line number="129" hits="1" branch="false"/>
<line number="134" hits="1" branch="false"/>
<line number="149" hits="1" branch="false"/>
<line number="142" hits="1" branch="false"/>
<line number="145" hits="1" branch="false"/>
<line number="103" hits="1" branch="false"/>
<line number="115" hits="1" branch="false"/>
<line number="92" hits="1" branch="false"/>
<line number="113" hits="1" branch="false"/>
<line number="144" hits="1" branch="false"/>
<line number="70" hits="1" branch="false"/>
<line number="73" hits="1" branch="false"/>
<line number="138" hits="1" branch="false"/>
<line number="143" hits="1" branch="false"/>
<line number="146" hits="1" branch="false"/>
<line number="105" hits="1" branch="false"/>
<line number="129" hits="1" branch="false"/>
<line number="49" hits="1" branch="false"/>
<line number="59" hits="1" branch="false"/>
<line number="123" hits="1" branch="false"/>
<line number="132" hits="1" branch="false"/>
<line number="34" hits="1" branch="false"/>
<line number="86" hits="1" branch="false"/>
<line number="144" hits="1" branch="false"/>
<line number="27" hits="1" branch="false"/>
<line number="110" hits="1" branch="false"/>
<line number="116" hits="1" branch="false"/>
<line number="64" hits="1" branch="false"/>
<line number="94" hits="1" branch="false"/>
<line number="71" hits="1" branch="false"/>
<line number="103" hits="1" branch="false"/>
<line number="147" hits="1" branch="false"/>
<line number="142" hits="1" branch="false"/>
</lines>
</class>
</classes>
</package>
<package name="com.example.utils" line-rate="1.00" branch-rate="1.00" complexity="0">
<classes>
<class
name="com.example.utils.Util" filename="com/example/utils/Util.scala" line-rate="1.00" branch-rate="1.00" complexity="0">
<methods>
<method
name="com.example.utils/DateFormatUtils/utilMethod" signature="()V" line-rate="1.00" branch-rate="1.00" complexity="0">
<lines>
<line number="13" hits="1" branch="false"/>
</lines>
</method>
<method
name="com.example.utils/DateFormatUtils/utilFunc" signature="()V" line-rate="1.00" branch-rate="1.00" complexity="0">
<lines>
<line number="8" hits="1" branch="false"/>
<line number="7" hits="1" branch="false"/>
<line number="8" hits="1" branch="false"/>
</lines>
</method>
</methods>
<lines>
<line number="8" hits="1" branch="false"/>
<line number="7" hits="1" branch="false"/>
<line number="13" hits="1" branch="false"/>
<line number="8" hits="1" branch="false"/>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,357 @@
<?xml version="1.0" ?>
<coverage lines-valid="524" lines-covered="455" line-rate="0.8683" branches-valid="653" branches-covered="465" branch-rate="0.7121" timestamp="1575309673957" complexity="0" version="0.1">
<packages>
<package name="src" line-rate="1" branch-rate="1">
<classes>
<class name="index.ts" filename="src/index.ts" line-rate="1" branch-rate="1">
<methods>
</methods>
<lines>
</lines>
</class>
</classes>
</package>
<package name="src.atoms.BoxShadow" line-rate="1" branch-rate="1">
<classes>
<class name="box-shadow.ts" filename="src/atoms/BoxShadow/box-shadow.ts" line-rate="1" branch-rate="1">
<methods>
</methods>
<lines>
<line number="4" hits="5" branch="false"/>
</lines>
</class>
<class name="index.ts" filename="src/atoms/BoxShadow/index.ts" line-rate="1" branch-rate="1">
<methods>
</methods>
<lines>
</lines>
</class>
</classes>
</package>
<package name="src.atoms.Button" line-rate="0.4706" branch-rate="0.7646999999999999">
<classes>
<class name="button.tsx" filename="src/atoms/Button/button.tsx" line-rate="0.4706" branch-rate="0.7646999999999999">
<methods>
<method name="ButtonComponent" hits="41" signature="()V">
<lines>
<line number="17" hits="41"/>
</lines>
</method>
<method name="(anonymous_1)" hits="0" signature="()V">
<lines>
<line number="34" hits="0"/>
</lines>
</method>
<method name="(anonymous_2)" hits="41" signature="()V">
<lines>
<line number="70" hits="41"/>
</lines>
</method>
<method name="(anonymous_3)" hits="41" signature="()V">
<lines>
<line number="72" hits="41"/>
</lines>
</method>
<method name="(anonymous_4)" hits="41" signature="()V">
<lines>
<line number="74" hits="41"/>
</lines>
</method>
</methods>
<lines>
<line number="31" hits="41" branch="false"/>
<line number="32" hits="41" branch="false"/>
<line number="34" hits="41" branch="false"/>
<line number="35" hits="0" branch="true" condition-coverage="0% (0/4)"/>
<line number="36" hits="0" branch="false"/>
<line number="37" hits="0" branch="false"/>
<line number="38" hits="0" branch="false"/>
<line number="39" hits="0" branch="false"/>
<line number="40" hits="0" branch="false"/>
<line number="41" hits="0" branch="false"/>
<line number="42" hits="0" branch="false"/>
<line number="43" hits="0" branch="false"/>
<line number="47" hits="41" branch="false"/>
<line number="68" hits="10" branch="false"/>
<line number="70" hits="41" branch="true" condition-coverage="100% (1/1)"/>
<line number="72" hits="41" branch="true" condition-coverage="100% (3/3)"/>
<line number="75" hits="41" branch="true" condition-coverage="100% (2/2)"/>
</lines>
</class>
<class name="index.ts" filename="src/atoms/Button/index.ts" line-rate="1" branch-rate="1">
<methods>
</methods>
<lines>
</lines>
</class>
</classes>
</package>
<package name="src.atoms.Container" line-rate="1" branch-rate="1">
<classes>
<class name="components.ts" filename="src/atoms/Container/components.ts" line-rate="1" branch-rate="1">
<methods>
<method name="(anonymous_0)" hits="35" signature="()V">
<lines>
<line number="5" hits="35"/>
</lines>
</method>
<method name="(anonymous_1)" hits="35" signature="()V">
<lines>
<line number="19" hits="35"/>
</lines>
</method>
<method name="(anonymous_2)" hits="35" signature="()V">
<lines>
<line number="20" hits="35"/>
</lines>
</method>
<method name="(anonymous_3)" hits="35" signature="()V">
<lines>
<line number="23" hits="35"/>
</lines>
</method>
</methods>
<lines>
<line number="4" hits="13" branch="false"/>
<line number="5" hits="35" branch="false"/>
<line number="13" hits="13" branch="false"/>
<line number="19" hits="35" branch="false"/>
<line number="20" hits="35" branch="false"/>
<line number="23" hits="35" branch="false"/>
</lines>
</class>
<class name="container.tsx" filename="src/atoms/Container/container.tsx" line-rate="1" branch-rate="1">
<methods>
<method name="Container" hits="35" signature="()V">
<lines>
<line number="12" hits="35"/>
</lines>
</method>
</methods>
<lines>
<line number="20" hits="35" branch="false"/>
</lines>
</class>
<class name="index.ts" filename="src/atoms/Container/index.ts" line-rate="1" branch-rate="1">
<methods>
</methods>
<lines>
</lines>
</class>
</classes>
</package>
<package name="src.atoms.DownArrow" line-rate="1" branch-rate="1">
<classes>
<class name="down-arrow.tsx" filename="src/atoms/DownArrow/down-arrow.tsx" line-rate="1" branch-rate="1">
<methods>
<method name="DownArrow" hits="28" signature="()V">
<lines>
<line number="3" hits="28"/>
</lines>
</method>
</methods>
<lines>
<line number="4" hits="28" branch="false"/>
</lines>
</class>
<class name="index.ts" filename="src/atoms/DownArrow/index.ts" line-rate="1" branch-rate="1">
<methods>
</methods>
<lines>
</lines>
</class>
</classes>
</package>
<package name="src.molecules.Hero" line-rate="0.8695999999999999" branch-rate="0.7236">
<classes>
<class name="components.ts" filename="src/molecules/Hero/components.ts" line-rate="0.8769" branch-rate="0.7143">
<methods>
<method name="(anonymous_0)" hits="10" signature="()V">
<lines>
<line number="26" hits="10"/>
</lines>
</method>
<method name="(anonymous_1)" hits="10" signature="()V">
<lines>
<line number="28" hits="10"/>
</lines>
</method>
<method name="(anonymous_2)" hits="10" signature="()V">
<lines>
<line number="94" hits="10"/>
</lines>
</method>
<method name="(anonymous_3)" hits="10" signature="()V">
<lines>
<line number="103" hits="10"/>
</lines>
</method>
<method name="(anonymous_4)" hits="10" signature="()V">
<lines>
<line number="121" hits="10"/>
</lines>
</method>
<method name="(anonymous_5)" hits="10" signature="()V">
<lines>
<line number="182" hits="10"/>
</lines>
</method>
<method name="(anonymous_6)" hits="5" signature="()V">
<lines>
<line number="203" hits="5"/>
</lines>
</method>
<method name="(anonymous_7)" hits="5" signature="()V">
<lines>
<line number="218" hits="5"/>
</lines>
</method>
<method name="(anonymous_8)" hits="5" signature="()V">
<lines>
<line number="236" hits="5"/>
</lines>
</method>
<method name="(anonymous_9)" hits="5" signature="()V">
<lines>
<line number="241" hits="5"/>
</lines>
</method>
<method name="(anonymous_10)" hits="4" signature="()V">
<lines>
<line number="249" hits="4"/>
</lines>
</method>
<method name="(anonymous_11)" hits="4" signature="()V">
<lines>
<line number="254" hits="4"/>
</lines>
</method>
<method name="(anonymous_12)" hits="4" signature="()V">
<lines>
<line number="264" hits="4"/>
</lines>
</method>
<method name="(anonymous_13)" hits="4" signature="()V">
<lines>
<line number="277" hits="4"/>
</lines>
</method>
<method name="(anonymous_14)" hits="4" signature="()V">
<lines>
<line number="282" hits="4"/>
</lines>
</method>
<method name="(anonymous_15)" hits="4" signature="()V">
<lines>
<line number="294" hits="4"/>
</lines>
</method>
<method name="(anonymous_16)" hits="3" signature="()V">
<lines>
<line number="299" hits="3"/>
</lines>
</method>
</methods>
<lines>
<line number="25" hits="2" branch="false"/>
<line number="26" hits="10" branch="false"/>
<line number="29" hits="10" branch="true" condition-coverage="100% (2/2)"/>
<line number="37" hits="3" branch="false"/>
<line number="39" hits="3" branch="true" condition-coverage="100% (2/2)"/>
<line number="40" hits="3" branch="false"/>
<line number="42" hits="3" branch="true" condition-coverage="100% (2/2)"/>
<line number="43" hits="1" branch="true" condition-coverage="75% (3/4)"/>
<line number="44" hits="1" branch="false"/>
<line number="45" hits="0" branch="true" condition-coverage="0% (0/4)"/>
<line number="46" hits="0" branch="false"/>
<line number="48" hits="1" branch="true" condition-coverage="50% (1/2)"/>
<line number="52" hits="0" branch="false"/>
<line number="53" hits="1" branch="true" condition-coverage="50% (1/2)"/>
<line number="57" hits="0" branch="false"/>
<line number="59" hits="1" branch="true" condition-coverage="50% (1/2)"/>
<line number="60" hits="1" branch="true" condition-coverage="50% (1/2)"/>
<line number="63" hits="3" branch="false"/>
<line number="95" hits="10" branch="true" condition-coverage="50% (1/2)"/>
<line number="103" hits="10" branch="false"/>
<line number="112" hits="2" branch="false"/>
<line number="122" hits="10" branch="true" condition-coverage="50% (2/4)"/>
<line number="125" hits="9" branch="false"/>
<line number="134" hits="0" branch="false"/>
<line number="151" hits="1" branch="false"/>
<line number="170" hits="2" branch="false"/>
<line number="181" hits="2" branch="false"/>
<line number="182" hits="10" branch="true" condition-coverage="100% (2/2)"/>
<line number="194" hits="2" branch="false"/>
<line number="204" hits="5" branch="true" condition-coverage="80% (4/5)"/>
<line number="207" hits="3" branch="false"/>
<line number="209" hits="0" branch="false"/>
<line number="211" hits="1" branch="false"/>
<line number="213" hits="1" branch="false"/>
<line number="219" hits="5" branch="true" condition-coverage="66.66666666666666% (4/6)"/>
<line number="222" hits="3" branch="false"/>
<line number="224" hits="0" branch="false"/>
<line number="226" hits="0" branch="false"/>
<line number="228" hits="1" branch="false"/>
<line number="230" hits="1" branch="false"/>
<line number="236" hits="5" branch="true" condition-coverage="100% (1/1)"/>
<line number="241" hits="5" branch="true" condition-coverage="100% (1/1)"/>
<line number="245" hits="2" branch="false"/>
<line number="249" hits="4" branch="true" condition-coverage="50% (1/2)"/>
<line number="255" hits="4" branch="true" condition-coverage="100% (3/3)"/>
<line number="258" hits="3" branch="false"/>
<line number="260" hits="1" branch="false"/>
<line number="265" hits="4" branch="true" condition-coverage="100% (4/4)"/>
<line number="268" hits="2" branch="false"/>
<line number="270" hits="1" branch="false"/>
<line number="272" hits="1" branch="false"/>
<line number="277" hits="4" branch="true" condition-coverage="100% (1/1)"/>
<line number="283" hits="4" branch="true" condition-coverage="100% (4/4)"/>
<line number="286" hits="2" branch="false"/>
<line number="288" hits="1" branch="false"/>
<line number="290" hits="1" branch="false"/>
<line number="294" hits="4" branch="true" condition-coverage="50% (1/2)"/>
<line number="298" hits="2" branch="false"/>
<line number="299" hits="3" branch="true" condition-coverage="50% (1/2)"/>
<line number="304" hits="2" branch="false"/>
<line number="311" hits="2" branch="false"/>
<line number="319" hits="2" branch="false"/>
<line number="329" hits="2" branch="false"/>
<line number="361" hits="2" branch="false"/>
<line number="371" hits="2" branch="false"/>
</lines>
</class>
<class name="hero.tsx" filename="src/molecules/Hero/hero.tsx" line-rate="0.75" branch-rate="0.76">
<methods>
<method name="Hero" hits="10" signature="()V">
<lines>
<line number="40" hits="10"/>
</lines>
</method>
<method name="(anonymous_1)" hits="0" signature="()V">
<lines>
<line number="72" hits="0"/>
</lines>
</method>
<method name="(anonymous_2)" hits="3" signature="()V">
<lines>
<line number="127" hits="3"/>
</lines>
</method>
</methods>
<lines>
<line number="56" hits="10" branch="false"/>
<line number="58" hits="10" branch="false"/>
<line number="73" hits="0" branch="true" condition-coverage="0% (0/2)"/>
<line number="128" hits="3" branch="false"/>
</lines>
</class>
<class name="index.ts" filename="src/molecules/Hero/index.ts" line-rate="1" branch-rate="1">
<methods>
</methods>
<lines>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>
@@ -0,0 +1,348 @@
<?xml version="1.0" ?>
<coverage line-rate="0.0" lines-covered="0" lines-valid="325" version="0.1">
<package line-rate="0.0" name="Sources">
<classes>
<class filename="Sources/TDSOptions.m" line-rate="0.0" name="TDSOptions.m">
<lines>
<line hits="0" number="32" />
<line hits="0" number="33" />
<line hits="0" number="34" />
<line hits="0" number="35" />
<line hits="0" number="36" />
<line hits="0" number="37" />
<line hits="0" number="38" />
<line hits="0" number="45" />
<line hits="0" number="46" />
<line hits="0" number="47" />
<line hits="0" number="48" />
<line hits="0" number="49" />
<line hits="0" number="50" />
<line hits="0" number="51" />
<line hits="0" number="52" />
<line hits="0" number="53" />
<line hits="0" number="54" />
<line hits="0" number="55" />
<line hits="0" number="56" />
<line hits="0" number="57" />
<line hits="0" number="58" />
<line hits="0" number="61" />
<line hits="0" number="62" />
<line hits="0" number="63" />
<line hits="0" number="66" />
<line hits="0" number="67" />
<line hits="0" number="68" />
<line hits="0" number="69" />
<line hits="0" number="70" />
<line hits="0" number="71" />
<line hits="0" number="72" />
<line hits="0" number="73" />
<line hits="0" number="74" />
<line hits="0" number="75" />
<line hits="0" number="76" />
<line hits="0" number="77" />
</lines>
</class>
<class filename="Sources/TDSUserLock.m" line-rate="0.0" name="TDSUserLock.m">
<lines>
<line hits="0" number="17" />
<line hits="0" number="18" />
<line hits="0" number="19" />
<line hits="0" number="20" />
<line hits="0" number="21" />
<line hits="0" number="22" />
<line hits="0" number="23" />
<line hits="0" number="24" />
<line hits="0" number="25" />
<line hits="0" number="26" />
<line hits="0" number="27" />
<line hits="0" number="28" />
<line hits="0" number="29" />
<line hits="0" number="30" />
<line hits="0" number="31" />
<line hits="0" number="32" />
<line hits="0" number="33" />
<line hits="0" number="34" />
<line hits="0" number="35" />
<line hits="0" number="36" />
<line hits="0" number="37" />
<line hits="0" number="38" />
<line hits="0" number="39" />
<line hits="0" number="40" />
<line hits="0" number="43" />
<line hits="0" number="44" />
<line hits="0" number="45" />
<line hits="0" number="46" />
<line hits="0" number="47" />
<line hits="0" number="48" />
<line hits="0" number="49" />
<line hits="0" number="58" />
<line hits="0" number="59" />
<line hits="0" number="60" />
<line hits="0" number="61" />
<line hits="0" number="62" />
<line hits="0" number="63" />
<line hits="0" number="64" />
<line hits="0" number="65" />
<line hits="0" number="66" />
<line hits="0" number="67" />
<line hits="0" number="68" />
<line hits="0" number="69" />
<line hits="0" number="70" />
</lines>
</class>
<class filename="Sources/TDSUserManager.m" line-rate="0.0" name="TDSUserManager.m">
<lines>
<line hits="0" number="24" />
<line hits="0" number="25" />
<line hits="0" number="26" />
<line hits="0" number="27" />
<line hits="0" number="28" />
<line hits="0" number="29" />
<line hits="0" number="30" />
<line hits="0" number="31" />
<line hits="0" number="34" />
<line hits="0" number="35" />
<line hits="0" number="36" />
<line hits="0" number="37" />
<line hits="0" number="38" />
<line hits="0" number="39" />
<line hits="0" number="40" />
<line hits="0" number="49" />
<line hits="0" number="50" />
<line hits="0" number="51" />
<line hits="0" number="52" />
<line hits="0" number="53" />
<line hits="0" number="54" />
<line hits="0" number="55" />
<line hits="0" number="56" />
<line hits="0" number="57" />
<line hits="0" number="58" />
<line hits="0" number="59" />
<line hits="0" number="60" />
<line hits="0" number="61" />
<line hits="0" number="62" />
<line hits="0" number="63" />
<line hits="0" number="64" />
<line hits="0" number="65" />
<line hits="0" number="66" />
<line hits="0" number="67" />
<line hits="0" number="68" />
<line hits="0" number="69" />
<line hits="0" number="70" />
<line hits="0" number="74" />
<line hits="0" number="75" />
<line hits="0" number="76" />
<line hits="0" number="77" />
<line hits="0" number="78" />
<line hits="0" number="79" />
<line hits="0" number="80" />
<line hits="0" number="81" />
<line hits="0" number="82" />
<line hits="0" number="83" />
<line hits="0" number="84" />
<line hits="0" number="85" />
<line hits="0" number="86" />
<line hits="0" number="87" />
<line hits="0" number="88" />
<line hits="0" number="89" />
<line hits="0" number="90" />
<line hits="0" number="91" />
<line hits="0" number="92" />
<line hits="0" number="93" />
<line hits="0" number="94" />
<line hits="0" number="101" />
<line hits="0" number="102" />
<line hits="0" number="103" />
<line hits="0" number="104" />
<line hits="0" number="105" />
<line hits="0" number="106" />
<line hits="0" number="107" />
<line hits="0" number="108" />
<line hits="0" number="109" />
<line hits="0" number="110" />
<line hits="0" number="111" />
<line hits="0" number="112" />
<line hits="0" number="113" />
<line hits="0" number="114" />
<line hits="0" number="115" />
<line hits="0" number="116" />
<line hits="0" number="117" />
<line hits="0" number="118" />
<line hits="0" number="119" />
<line hits="0" number="120" />
<line hits="0" number="121" />
<line hits="0" number="122" />
<line hits="0" number="123" />
<line hits="0" number="124" />
<line hits="0" number="125" />
<line hits="0" number="126" />
<line hits="0" number="127" />
<line hits="0" number="128" />
<line hits="0" number="129" />
<line hits="0" number="130" />
<line hits="0" number="131" />
<line hits="0" number="136" />
<line hits="0" number="137" />
<line hits="0" number="138" />
<line hits="0" number="139" />
<line hits="0" number="140" />
<line hits="0" number="141" />
<line hits="0" number="142" />
<line hits="0" number="143" />
<line hits="0" number="144" />
<line hits="0" number="145" />
<line hits="0" number="146" />
<line hits="0" number="147" />
<line hits="0" number="148" />
<line hits="0" number="149" />
<line hits="0" number="150" />
<line hits="0" number="151" />
<line hits="0" number="152" />
<line hits="0" number="153" />
<line hits="0" number="158" />
<line hits="0" number="159" />
<line hits="0" number="160" />
<line hits="0" number="161" />
<line hits="0" number="162" />
<line hits="0" number="163" />
<line hits="0" number="164" />
<line hits="0" number="165" />
<line hits="0" number="166" />
<line hits="0" number="167" />
<line hits="0" number="168" />
<line hits="0" number="169" />
<line hits="0" number="170" />
<line hits="0" number="171" />
<line hits="0" number="172" />
<line hits="0" number="173" />
<line hits="0" number="174" />
<line hits="0" number="175" />
<line hits="0" number="176" />
<line hits="0" number="177" />
<line hits="0" number="178" />
<line hits="0" number="179" />
<line hits="0" number="180" />
<line hits="0" number="181" />
<line hits="0" number="182" />
<line hits="0" number="183" />
<line hits="0" number="184" />
<line hits="0" number="189" />
<line hits="0" number="190" />
<line hits="0" number="191" />
<line hits="0" number="192" />
<line hits="0" number="193" />
<line hits="0" number="194" />
<line hits="0" number="195" />
<line hits="0" number="196" />
<line hits="0" number="197" />
<line hits="0" number="198" />
<line hits="0" number="199" />
<line hits="0" number="200" />
<line hits="0" number="201" />
<line hits="0" number="202" />
<line hits="0" number="203" />
<line hits="0" number="204" />
<line hits="0" number="205" />
<line hits="0" number="206" />
<line hits="0" number="207" />
<line hits="0" number="208" />
<line hits="0" number="209" />
<line hits="0" number="210" />
<line hits="0" number="211" />
<line hits="0" number="212" />
<line hits="0" number="213" />
<line hits="0" number="214" />
<line hits="0" number="215" />
<line hits="0" number="216" />
<line hits="0" number="217" />
<line hits="0" number="218" />
<line hits="0" number="219" />
<line hits="0" number="220" />
<line hits="0" number="221" />
<line hits="0" number="222" />
<line hits="0" number="223" />
<line hits="0" number="224" />
<line hits="0" number="225" />
<line hits="0" number="226" />
<line hits="0" number="227" />
<line hits="0" number="228" />
<line hits="0" number="229" />
<line hits="0" number="230" />
<line hits="0" number="231" />
<line hits="0" number="232" />
<line hits="0" number="233" />
<line hits="0" number="234" />
<line hits="0" number="235" />
<line hits="0" number="236" />
<line hits="0" number="239" />
<line hits="0" number="240" />
<line hits="0" number="241" />
<line hits="0" number="242" />
<line hits="0" number="243" />
<line hits="0" number="244" />
<line hits="0" number="245" />
<line hits="0" number="246" />
<line hits="0" number="247" />
<line hits="0" number="248" />
<line hits="0" number="249" />
<line hits="0" number="252" />
<line hits="0" number="253" />
<line hits="0" number="254" />
<line hits="0" number="255" />
<line hits="0" number="256" />
<line hits="0" number="257" />
<line hits="0" number="258" />
<line hits="0" number="259" />
<line hits="0" number="260" />
<line hits="0" number="261" />
<line hits="0" number="262" />
<line hits="0" number="263" />
<line hits="0" number="264" />
<line hits="0" number="265" />
<line hits="0" number="268" />
<line hits="0" number="269" />
<line hits="0" number="270" />
<line hits="0" number="271" />
<line hits="0" number="272" />
<line hits="0" number="273" />
<line hits="0" number="274" />
<line hits="0" number="275" />
<line hits="0" number="276" />
<line hits="0" number="277" />
<line hits="0" number="278" />
<line hits="0" number="279" />
<line hits="0" number="280" />
<line hits="0" number="281" />
<line hits="0" number="282" />
<line hits="0" number="283" />
<line hits="0" number="284" />
<line hits="0" number="287" />
<line hits="0" number="288" />
<line hits="0" number="289" />
<line hits="0" number="290" />
<line hits="0" number="291" />
<line hits="0" number="292" />
<line hits="0" number="293" />
<line hits="0" number="294" />
<line hits="0" number="295" />
<line hits="0" number="296" />
</lines>
</class>
<class filename="Sources/TDSUtil.m" line-rate="0.0" name="TDSUtil.m">
<lines>
<line hits="0" number="8" />
<line hits="0" number="9" />
<line hits="0" number="10" />
<line hits="0" number="11" />
<line hits="0" number="12" />
<line hits="0" number="13" />
<line hits="0" number="16" />
<line hits="0" number="17" />
<line hits="0" number="18" />
<line hits="0" number="19" />
<line hits="0" number="20" />
</lines>
</class>
</classes>
</package>
</coverage>
@@ -0,0 +1,441 @@
<?xml version="1.0" ?>
<coverage branch-rate="0.7602339181286549" branches-covered="130" branches-valid="171" complexity="0" line-rate="0.8738738738738738" lines-covered="582" lines-valid="666" timestamp="1575668573" version="2.0.3">
<sources>
<source>.</source>
</sources>
<packages>
<package branch-rate="0.75" complexity="0" line-rate="0.9672131147540983" name="src.graphql">
<classes>
<class branch-rate="0.0" complexity="0" filename="src/graphql/queries.ts" line-rate="1.0" name="src.graphql.queries.ts">
<methods/>
<lines>
<line branch="false" hits="3" number="1"/>
<line branch="false" hits="3" number="3"/>
<line branch="false" hits="3" number="50"/>
<line branch="false" hits="3" number="65"/>
<line branch="false" hits="3" number="80"/>
<line branch="false" hits="3" number="92"/>
<line branch="false" hits="3" number="138"/>
</lines>
</class>
<class branch-rate="0.8" complexity="0" filename="src/graphql/resolvers.ts" line-rate="0.974025974025974" name="src.graphql.resolvers.ts">
<methods>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_12)" signature="">
<lines>
<line branch="false" hits="17" number="22"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_13)" signature="">
<lines>
<line branch="false" hits="10" number="28"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_14)" signature="">
<lines>
<line branch="false" hits="12" number="36"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_15)" signature="">
<lines>
<line branch="false" hits="4" number="27"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_16)" signature="">
<lines>
<line branch="false" hits="4" number="35"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_17)" signature="">
<lines>
<line branch="false" hits="9" number="43"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_18)" signature="">
<lines>
<line branch="false" hits="11" number="51"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_19)" signature="">
<lines>
<line branch="false" hits="4" number="56"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_20)" signature="">
<lines>
<line branch="false" hits="4" number="61"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_21)" signature="">
<lines>
<line branch="false" hits="4" number="50"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_22)" signature="">
<lines>
<line branch="false" hits="4" number="55"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_23)" signature="">
<lines>
<line branch="false" hits="4" number="60"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_24)" signature="">
<lines>
<line branch="false" hits="7" number="128"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_25)" signature="">
<lines>
<line branch="false" hits="4" number="139"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_26)" signature="">
<lines>
<line branch="false" hits="4" number="145"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_28)" signature="">
<lines>
<line branch="false" hits="4" number="160"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_30)" signature="">
<lines>
<line branch="false" hits="3" number="177"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_31)" signature="">
<lines>
<line branch="false" hits="9" number="185"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_32)" signature="">
<lines>
<line branch="false" hits="8" number="70"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_33)" signature="">
<lines>
<line branch="false" hits="4" number="78"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_34)" signature="">
<lines>
<line branch="false" hits="4" number="81"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_35)" signature="">
<lines>
<line branch="false" hits="4" number="84"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_36)" signature="">
<lines>
<line branch="false" hits="4" number="97"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_37)" signature="">
<lines>
<line branch="false" hits="4" number="97"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_38)" signature="">
<lines>
<line branch="false" hits="4" number="103"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_39)" signature="">
<lines>
<line branch="false" hits="4" number="114"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_40)" signature="">
<lines>
<line branch="false" hits="4" number="144"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_41)" signature="">
<lines>
<line branch="false" hits="4" number="159"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_42)" signature="">
<lines>
<line branch="false" hits="4" number="176"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_43)" signature="">
<lines>
<line branch="false" hits="4" number="184"/>
</lines>
</method>
</methods>
<lines>
<line branch="false" hits="4" number="1"/>
<line branch="false" hits="4" number="2"/>
<line branch="false" hits="4" number="3"/>
<line branch="false" hits="4" number="5"/>
<line branch="false" hits="4" number="6"/>
<line branch="false" hits="4" number="7"/>
<line branch="false" hits="4" number="8"/>
<line branch="false" hits="4" number="9"/>
<line branch="false" hits="4" number="10"/>
<line branch="false" hits="4" number="14"/>
<line branch="false" hits="4" number="21"/>
<line branch="false" hits="17" number="24"/>
<line branch="false" hits="4" number="27"/>
<line branch="false" hits="4" number="28"/>
<line branch="false" hits="10" number="32"/>
<line branch="false" hits="4" number="35"/>
<line branch="false" hits="4" number="36"/>
<line branch="false" hits="12" number="37"/>
<line branch="false" hits="4" number="42"/>
<line branch="false" hits="9" number="45"/>
<line branch="false" hits="9" number="47"/>
<line branch="false" hits="4" number="50"/>
<line branch="false" hits="4" number="51"/>
<line branch="false" hits="11" number="52"/>
<line branch="false" hits="4" number="55"/>
<line branch="false" hits="4" number="56"/>
<line branch="false" hits="4" number="57"/>
<line branch="false" hits="4" number="60"/>
<line branch="false" hits="4" number="61"/>
<line branch="false" hits="4" number="62"/>
<line branch="false" hits="4" number="67"/>
<line branch="false" hits="4" number="70"/>
<line branch="false" hits="8" number="73"/>
<line branch="false" hits="4" number="78"/>
<line branch="false" hits="4" number="81"/>
<line branch="false" hits="4" number="83"/>
<line branch="false" hits="4" number="85"/>
<line branch="true" condition-coverage="100% (2/2)" hits="4" number="86"/>
<line branch="false" hits="4" number="87"/>
<line branch="false" hits="4" number="88"/>
<line branch="false" hits="4" number="94"/>
<line branch="false" hits="4" number="95"/>
<line branch="false" hits="4" number="97"/>
<line branch="false" hits="4" number="98"/>
<line branch="false" hits="4" number="103"/>
<line branch="false" hits="4" number="104"/>
<line branch="false" hits="4" number="107"/>
<line branch="false" hits="4" number="114"/>
<line branch="false" hits="4" number="115"/>
<line branch="false" hits="4" number="118"/>
<line branch="true" condition-coverage="50% (1/2)" hits="4" number="119"/>
<line branch="false" hits="0" number="120"/>
<line branch="false" hits="4" number="124"/>
<line branch="false" hits="7" number="130"/>
<line branch="false" hits="7" number="132"/>
<line branch="false" hits="7" number="134"/>
<line branch="false" hits="7" number="136"/>
<line branch="false" hits="4" number="140"/>
<line branch="false" hits="4" number="144"/>
<line branch="false" hits="4" number="145"/>
<line branch="false" hits="4" number="151"/>
<line branch="false" hits="4" number="159"/>
<line branch="false" hits="4" number="160"/>
<line branch="false" hits="4" number="165"/>
<line branch="true" condition-coverage="100% (2/2)" hits="4" number="166"/>
<line branch="true" condition-coverage="100% (2/2)" hits="4" number="167"/>
<line branch="false" hits="1" number="168"/>
<line branch="false" hits="4" number="169"/>
<line branch="false" hits="4" number="176"/>
<line branch="false" hits="4" number="177"/>
<line branch="false" hits="3" number="178"/>
<line branch="false" hits="4" number="184"/>
<line branch="false" hits="4" number="185"/>
<line branch="false" hits="9" number="186"/>
<line branch="false" hits="9" number="187"/>
<line branch="true" condition-coverage="50% (1/2)" hits="2" number="193"/>
<line branch="false" hits="0" number="194"/>
</lines>
</class>
<class branch-rate="0.7272727272727273" complexity="0" filename="src/graphql/router.ts" line-rate="0.9444444444444444" name="src.graphql.router.ts">
<methods>
<method branch-rate="1.0" line-rate="1.0" name="handleApolloClientError" signature="">
<lines>
<line branch="false" hits="1" number="21"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_8)" signature="">
<lines>
<line branch="false" hits="1" number="22"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="graphqlRoute" signature="">
<lines>
<line branch="false" hits="18" number="43"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_10)" signature="">
<lines>
<line branch="false" hits="11" number="53"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_11)" signature="">
<lines>
<line branch="false" hits="11" number="53"/>
</lines>
</method>
</methods>
<lines>
<line branch="false" hits="3" number="12"/>
<line branch="false" hits="3" number="14"/>
<line branch="false" hits="1" number="22"/>
<line branch="true" condition-coverage="100% (3/3)" hits="1" number="23"/>
<line branch="true" condition-coverage="50% (1/2)" hits="0" number="26"/>
<line branch="true" condition-coverage="50% (1/2)" hits="1" number="32"/>
<line branch="false" hits="1" number="33"/>
<line branch="false" hits="1" number="34"/>
<line branch="false" hits="0" number="37"/>
<line branch="false" hits="3" number="43"/>
<line branch="false" hits="18" number="53"/>
<line branch="false" hits="11" number="54"/>
<line branch="true" condition-coverage="100% (2/2)" hits="0" number="55"/>
<line branch="false" hits="11" number="57"/>
<line branch="false" hits="11" number="58"/>
<line branch="false" hits="11" number="63"/>
<line branch="false" hits="11" number="64"/>
<line branch="true" condition-coverage="50% (1/2)" hits="10" number="65"/>
<line branch="false" hits="10" number="66"/>
<line branch="false" hits="1" number="68"/>
</lines>
</class>
<class branch-rate="0.0" complexity="0" filename="src/graphql/schema.ts" line-rate="1.0" name="src.graphql.schema.ts">
<methods>
<method branch-rate="1.0" line-rate="1.0" name="getGraphqlSchema" signature="">
<lines>
<line branch="false" hits="4" number="9"/>
</lines>
</method>
</methods>
<lines>
<line branch="false" hits="3" number="1"/>
<line branch="false" hits="3" number="2"/>
<line branch="false" hits="3" number="4"/>
<line branch="false" hits="3" number="5"/>
<line branch="false" hits="3" number="9"/>
<line branch="true" condition-coverage="0% (0/1)" hits="0" number="11"/>
<line branch="false" hits="4" number="13"/>
<line branch="false" hits="4" number="15"/>
<line branch="false" hits="4" number="16"/>
<line branch="false" hits="4" number="17"/>
<line branch="false" hits="4" number="19"/>
</lines>
</class>
<class branch-rate="0.8333333333333334" complexity="0" filename="src/graphql/transforms.ts" line-rate="0.9" name="src.graphql.transforms.ts">
<methods>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_0)" signature="">
<lines>
<line branch="false" hits="6" number="9"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="(anonymous_1)" signature="">
<lines>
<line branch="false" hits="1" number="20"/>
</lines>
</method>
</methods>
<lines>
<line branch="false" hits="4" number="9"/>
<line branch="true" condition-coverage="100% (2/2)" hits="6" number="13"/>
<line branch="false" hits="2" number="14"/>
<line branch="true" condition-coverage="100% (2/2)" hits="6" number="15"/>
<line branch="false" hits="1" number="16"/>
<line branch="false" hits="6" number="17"/>
<line branch="false" hits="4" number="20"/>
<line branch="true" condition-coverage="50% (1/2)" hits="1" number="23"/>
<line branch="false" hits="0" number="24"/>
<line branch="false" hits="1" number="25"/>
</lines>
</class>
</classes>
</package>
<package branch-rate="0.0" complexity="0" line-rate="0.0" name="src.workers">
<classes>
<class branch-rate="0.0" complexity="0" filename="src/workers/primary-worker.js" line-rate="0.0" name="src.workers.primary-worker.js">
<methods>
<method branch-rate="0.0" line-rate="0.0" name="doWork" signature="">
<lines>
<line branch="false" hits="0" number="16"/>
</lines>
</method>
<method branch-rate="0.0" line-rate="0.0" name="(anonymous_9)" signature="">
<lines>
<line branch="false" hits="0" number="25"/>
</lines>
</method>
<method branch-rate="0.0" line-rate="0.0" name="(anonymous_10)" signature="">
<lines>
<line branch="false" hits="0" number="49"/>
</lines>
</method>
</methods>
<lines>
<line branch="false" hits="0" number="2"/>
<line branch="false" hits="0" number="3"/>
<line branch="false" hits="0" number="4"/>
<line branch="false" hits="0" number="5"/>
<line branch="false" hits="0" number="6"/>
<line branch="false" hits="0" number="8"/>
<line branch="false" hits="0" number="16"/>
<line branch="true" condition-coverage="0% (0/1)" hits="0" number="17"/>
<line branch="true" condition-coverage="0% (0/2)" hits="0" number="21"/>
<line branch="false" hits="0" number="22"/>
<line branch="false" hits="0" number="23"/>
<line branch="false" hits="0" number="24"/>
<line branch="false" hits="0" number="25"/>
<line branch="false" hits="0" number="27"/>
<line branch="false" hits="0" number="28"/>
<line branch="false" hits="0" number="31"/>
<line branch="false" hits="0" number="33"/>
<line branch="false" hits="0" number="34"/>
<line branch="false" hits="0" number="37"/>
<line branch="false" hits="0" number="38"/>
<line branch="false" hits="0" number="48"/>
<line branch="true" condition-coverage="0% (0/2)" hits="0" number="49"/>
<line branch="false" hits="0" number="51"/>
<line branch="false" hits="0" number="53"/>
</lines>
</class>
<class branch-rate="0.0" complexity="0" filename="src/workers/secondary-worker.js" line-rate="0.0" name="src.workers.secondary-worker.js">
<methods>
<method branch-rate="0.0" line-rate="0.0" name="doWork" signature="">
<lines>
<line branch="false" hits="0" number="4"/>
</lines>
</method>
<method branch-rate="0.0" line-rate="0.0" name="(anonymous_9)" signature="">
<lines>
<line branch="false" hits="0" number="13"/>
</lines>
</method>
</methods>
<lines>
<line branch="false" hits="0" number="2"/>
<line branch="false" hits="0" number="4"/>
<line branch="true" condition-coverage="0% (0/1)" hits="0" number="5"/>
<line branch="true" condition-coverage="0% (0/2)" hits="0" number="9"/>
<line branch="false" hits="0" number="10"/>
<line branch="false" hits="0" number="11"/>
<line branch="false" hits="0" number="12"/>
<line branch="false" hits="0" number="13"/>
<line branch="false" hits="0" number="14"/>
<line branch="false" hits="0" number="15"/>
<line branch="false" hits="0" number="16"/>
<line branch="false" hits="0" number="18"/>
<line branch="false" hits="0" number="19"/>
<line branch="false" hits="0" number="22"/>
<line branch="false" hits="0" number="23"/>
<line branch="true" condition-coverage="0% (0/2)" hits="0" number="24"/>
<line branch="false" hits="0" number="25"/>
<line branch="false" hits="0" number="26"/>
<line branch="false" hits="0" number="27"/>
<line branch="false" hits="0" number="29"/>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>
@@ -0,0 +1,319 @@
[
{
"filename": "src/main/java/com/example/Main.kt",
"lineHits": {
"28": 0,
"31": 0,
"36": 0,
"37": 0,
"38": 0,
"39": 0,
"40": 0,
"41": 0,
"42": 0,
"43": 0,
"44": 0,
"45": 0,
"46": 0,
"47": 0,
"48": 0,
"49": 0,
"50": 0,
"52": 0,
"53": 0,
"56": 0,
"57": 0,
"58": 0,
"59": 0,
"60": 0,
"62": 0,
"65": 0,
"66": 0,
"67": 0,
"69": 0,
"70": 0,
"71": 0,
"72": 0,
"74": 0,
"76": 0,
"77": 0,
"78": 0,
"79": 0,
"81": 0,
"82": 0,
"83": 0,
"84": 0,
"85": 0,
"86": 0,
"87": 0,
"88": 0,
"89": 0,
"92": 0,
"93": 0
},
"branchHits": {}
},
{
"filename": "src/main/java/com/example/Parser.kt",
"lineHits": {
"5": 9,
"10": 7,
"12": 8,
"13": 2,
"14": 3,
"16": 28,
"17": 1,
"19": 4,
"20": 16,
"21": 18,
"22": 12,
"23": 5,
"24": 14,
"25": 22,
"26": 9,
"28": 7,
"29": 1,
"32": 2,
"34": 13,
"36": 0,
"37": 0,
"38": 0,
"39": 0,
"40": 0,
"43": 7,
"44": 0,
"46": 2,
"49": 7,
"50": 1,
"53": 5,
"54": 3,
"55": 18,
"56": 3,
"57": 1,
"58": 3,
"59": 3,
"60": 5,
"62": 1,
"64": 6,
"65": 2,
"69": 5,
"70": 14,
"71": 8,
"72": 20,
"73": 16,
"74": 22,
"75": 3,
"76": 2,
"77": 19,
"78": 1,
"79": 13,
"80": 5,
"81": 4,
"83": 4,
"84": 12,
"85": 10,
"86": 7,
"87": 9,
"94": 10,
"95": 5,
"96": 4,
"98": 9,
"99": 12,
"100": 10,
"101": 7,
"102": 4,
"103": 9,
"104": 10,
"105": 5,
"106": 10,
"107": 4,
"108": 3,
"109": 2,
"110": 5,
"111": 2,
"113": 1,
"114": 7,
"116": 3,
"117": 12,
"118": 26,
"119": 10,
"120": 7,
"122": 3,
"123": 9,
"124": 14,
"125": 2,
"126": 3
},
"branchHits": {
"12": {
"covered": 1,
"missed": 1,
"available": 2
},
"16": {
"covered": 2,
"missed": 2,
"available": 4
},
"24": {
"covered": 2,
"missed": 0,
"available": 2
},
"25": {
"covered": 6,
"missed": 2,
"available": 8
},
"38": {
"covered": 0,
"missed": 4,
"available": 4
},
"43": {
"covered": 1,
"missed": 1,
"available": 2
},
"44": {
"covered": 0,
"missed": 2,
"available": 2
},
"55": {
"covered": 4,
"missed": 0,
"available": 4
},
"70": {
"covered": 1,
"missed": 3,
"available": 4
},
"73": {
"covered": 2,
"missed": 0,
"available": 2
},
"75": {
"covered": 1,
"missed": 1,
"available": 2
},
"77": {
"covered": 2,
"missed": 0,
"available": 2
},
"79": {
"covered": 2,
"missed": 0,
"available": 2
},
"85": {
"covered": 2,
"missed": 0,
"available": 2
},
"94": {
"covered": 2,
"missed": 0,
"available": 2
},
"100": {
"covered": 2,
"missed": 0,
"available": 2
},
"104": {
"covered": 2,
"missed": 0,
"available": 2
},
"108": {
"covered": 2,
"missed": 0,
"available": 2
},
"119": {
"covered": 2,
"missed": 0,
"available": 2
},
"124": {
"covered": 2,
"missed": 0,
"available": 2
}
}
},
{
"filename": "src/main/java/com/example/models/Thing.kt",
"lineHits": {
"9": 8,
"10": 3,
"11": 0,
"14": 8,
"15": 3,
"16": 1,
"19": 8,
"20": 3,
"21": 0,
"24": 3,
"25": 3,
"26": 1,
"29": 11,
"30": 3,
"31": 3,
"32": 3,
"35": 12,
"36": 12,
"37": 1,
"40": 5,
"41": 3,
"44": 3,
"45": 1,
"48": 12,
"49": 3,
"50": 3,
"51": 0
},
"branchHits": {
"14": {
"covered": 2,
"missed": 0,
"available": 2
},
"15": {
"covered": 1,
"missed": 1,
"available": 2
},
"24": {
"covered": 1,
"missed": 1,
"available": 2
},
"25": {
"covered": 1,
"missed": 1,
"available": 2
},
"35": {
"covered": 2,
"missed": 0,
"available": 2
},
"36": {
"covered": 2,
"missed": 0,
"available": 2
},
"44": {
"covered": 1,
"missed": 1,
"available": 2
}
}
}
]
@@ -0,0 +1,7 @@
pom.xml
src/main/java/com/example/Parser.kt
src/main/java/com/example/Main.kt
src/main/java/com/example/models/Thing.kt
src/main/resources/app.conf
src/test/java/com/example/ParserTest.kt
src/test/resources/logback-test.xml
@@ -0,0 +1,282 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<report name="example">
<sessioninfo id="743a5357362a-f32e3def" start="1575926125817" dump="1575926133882"/>
<package name="com/example">
<sourcefile name="Main.kt">
<line nr="28" mi="6" ci="0" mb="0" cb="0"/>
<line nr="31" mi="7" ci="0" mb="0" cb="0"/>
<line nr="36" mi="3" ci="0" mb="0" cb="0"/>
<line nr="37" mi="12" ci="0" mb="0" cb="0"/>
<line nr="38" mi="2" ci="0" mb="0" cb="0"/>
<line nr="39" mi="2" ci="0" mb="0" cb="0"/>
<line nr="40" mi="5" ci="0" mb="0" cb="0"/>
<line nr="41" mi="5" ci="0" mb="0" cb="0"/>
<line nr="42" mi="3" ci="0" mb="0" cb="0"/>
<line nr="43" mi="3" ci="0" mb="0" cb="0"/>
<line nr="44" mi="3" ci="0" mb="0" cb="0"/>
<line nr="45" mi="3" ci="0" mb="0" cb="0"/>
<line nr="46" mi="3" ci="0" mb="0" cb="0"/>
<line nr="47" mi="4" ci="0" mb="0" cb="0"/>
<line nr="48" mi="4" ci="0" mb="0" cb="0"/>
<line nr="49" mi="4" ci="0" mb="0" cb="0"/>
<line nr="50" mi="1" ci="0" mb="0" cb="0"/>
<line nr="52" mi="8" ci="0" mb="0" cb="0"/>
<line nr="53" mi="19" ci="0" mb="0" cb="0"/>
<line nr="56" mi="1" ci="0" mb="0" cb="0"/>
<line nr="57" mi="8" ci="0" mb="0" cb="0"/>
<line nr="58" mi="1" ci="0" mb="0" cb="0"/>
<line nr="59" mi="24" ci="0" mb="0" cb="0"/>
<line nr="60" mi="2" ci="0" mb="0" cb="0"/>
<line nr="62" mi="1" ci="0" mb="0" cb="0"/>
<line nr="65" mi="5" ci="0" mb="0" cb="0"/>
<line nr="66" mi="5" ci="0" mb="0" cb="0"/>
<line nr="67" mi="3" ci="0" mb="0" cb="0"/>
<line nr="69" mi="1" ci="0" mb="0" cb="0"/>
<line nr="70" mi="1" ci="0" mb="0" cb="0"/>
<line nr="71" mi="1" ci="0" mb="0" cb="0"/>
<line nr="72" mi="7" ci="0" mb="0" cb="0"/>
<line nr="74" mi="2" ci="0" mb="0" cb="0"/>
<line nr="76" mi="3" ci="0" mb="0" cb="0"/>
<line nr="77" mi="7" ci="0" mb="0" cb="0"/>
<line nr="78" mi="5" ci="0" mb="0" cb="0"/>
<line nr="79" mi="5" ci="0" mb="0" cb="0"/>
<line nr="81" mi="1" ci="0" mb="0" cb="0"/>
<line nr="82" mi="1" ci="0" mb="0" cb="0"/>
<line nr="83" mi="2" ci="0" mb="0" cb="0"/>
<line nr="84" mi="9" ci="0" mb="0" cb="0"/>
<line nr="85" mi="3" ci="0" mb="0" cb="0"/>
<line nr="86" mi="1" ci="0" mb="0" cb="0"/>
<line nr="87" mi="3" ci="0" mb="0" cb="0"/>
<line nr="88" mi="1" ci="0" mb="0" cb="0"/>
<line nr="89" mi="6" ci="0" mb="0" cb="0"/>
<line nr="92" mi="6" ci="0" mb="0" cb="0"/>
<line nr="93" mi="1" ci="0" mb="0" cb="0"/>
<counter type="INSTRUCTION" missed="219" covered="0"/>
<counter type="LINE" missed="48" covered="0"/>
<counter type="COMPLEXITY" missed="8" covered="0"/>
<counter type="METHOD" missed="8" covered="0"/>
<counter type="CLASS" missed="6" covered="0"/>
</sourcefile>
<sourcefile name="Parser.kt">
<line nr="5" mi="0" ci="9" mb="0" cb="0"/>
<line nr="10" mi="0" ci="7" mb="0" cb="0"/>
<line nr="12" mi="1" ci="8" mb="1" cb="1"/>
<line nr="13" mi="0" ci="2" mb="0" cb="0"/>
<line nr="14" mi="0" ci="3" mb="0" cb="0"/>
<line nr="16" mi="2" ci="28" mb="2" cb="2"/>
<line nr="17" mi="0" ci="1" mb="0" cb="0"/>
<line nr="19" mi="0" ci="4" mb="0" cb="0"/>
<line nr="20" mi="0" ci="16" mb="0" cb="0"/>
<line nr="21" mi="0" ci="18" mb="0" cb="0"/>
<line nr="22" mi="0" ci="12" mb="0" cb="0"/>
<line nr="23" mi="0" ci="5" mb="0" cb="0"/>
<line nr="24" mi="0" ci="14" mb="0" cb="2"/>
<line nr="25" mi="1" ci="22" mb="2" cb="6"/>
<line nr="26" mi="0" ci="9" mb="0" cb="0"/>
<line nr="28" mi="0" ci="7" mb="0" cb="0"/>
<line nr="29" mi="0" ci="1" mb="0" cb="0"/>
<line nr="32" mi="0" ci="2" mb="0" cb="0"/>
<line nr="34" mi="0" ci="13" mb="0" cb="0"/>
<line nr="36" mi="2" ci="0" mb="0" cb="0"/>
<line nr="37" mi="9" ci="0" mb="0" cb="0"/>
<line nr="38" mi="37" ci="0" mb="4" cb="0"/>
<line nr="39" mi="3" ci="0" mb="0" cb="0"/>
<line nr="40" mi="1" ci="0" mb="0" cb="0"/>
<line nr="43" mi="0" ci="7" mb="1" cb="1"/>
<line nr="44" mi="20" ci="0" mb="2" cb="0"/>
<line nr="46" mi="0" ci="2" mb="0" cb="0"/>
<line nr="49" mi="0" ci="7" mb="0" cb="0"/>
<line nr="50" mi="0" ci="1" mb="0" cb="0"/>
<line nr="53" mi="0" ci="5" mb="0" cb="0"/>
<line nr="54" mi="0" ci="3" mb="0" cb="0"/>
<line nr="55" mi="0" ci="18" mb="0" cb="4"/>
<line nr="56" mi="0" ci="3" mb="0" cb="0"/>
<line nr="57" mi="0" ci="1" mb="0" cb="0"/>
<line nr="58" mi="0" ci="3" mb="0" cb="0"/>
<line nr="59" mi="0" ci="3" mb="0" cb="0"/>
<line nr="60" mi="0" ci="5" mb="0" cb="0"/>
<line nr="62" mi="0" ci="1" mb="0" cb="0"/>
<line nr="64" mi="0" ci="6" mb="0" cb="0"/>
<line nr="65" mi="0" ci="2" mb="0" cb="0"/>
<line nr="69" mi="2" ci="5" mb="0" cb="0"/>
<line nr="70" mi="17" ci="14" mb="3" cb="1"/>
<line nr="71" mi="1" ci="8" mb="0" cb="0"/>
<line nr="72" mi="0" ci="20" mb="0" cb="0"/>
<line nr="73" mi="0" ci="16" mb="0" cb="2"/>
<line nr="74" mi="0" ci="22" mb="0" cb="0"/>
<line nr="75" mi="2" ci="3" mb="1" cb="1"/>
<line nr="76" mi="0" ci="2" mb="0" cb="0"/>
<line nr="77" mi="0" ci="19" mb="0" cb="2"/>
<line nr="78" mi="0" ci="1" mb="0" cb="0"/>
<line nr="79" mi="0" ci="13" mb="0" cb="2"/>
<line nr="80" mi="0" ci="5" mb="0" cb="0"/>
<line nr="81" mi="0" ci="4" mb="0" cb="0"/>
<line nr="83" mi="0" ci="4" mb="0" cb="0"/>
<line nr="84" mi="0" ci="12" mb="0" cb="0"/>
<line nr="85" mi="0" ci="10" mb="0" cb="2"/>
<line nr="86" mi="0" ci="7" mb="0" cb="0"/>
<line nr="87" mi="0" ci="9" mb="0" cb="0"/>
<line nr="94" mi="0" ci="10" mb="0" cb="2"/>
<line nr="95" mi="0" ci="5" mb="0" cb="0"/>
<line nr="96" mi="0" ci="4" mb="0" cb="0"/>
<line nr="98" mi="0" ci="9" mb="0" cb="0"/>
<line nr="99" mi="0" ci="12" mb="0" cb="0"/>
<line nr="100" mi="0" ci="10" mb="0" cb="2"/>
<line nr="101" mi="0" ci="7" mb="0" cb="0"/>
<line nr="102" mi="0" ci="4" mb="0" cb="0"/>
<line nr="103" mi="0" ci="9" mb="0" cb="0"/>
<line nr="104" mi="0" ci="10" mb="0" cb="2"/>
<line nr="105" mi="0" ci="5" mb="0" cb="0"/>
<line nr="106" mi="0" ci="10" mb="0" cb="0"/>
<line nr="107" mi="0" ci="4" mb="0" cb="0"/>
<line nr="108" mi="0" ci="3" mb="0" cb="2"/>
<line nr="109" mi="0" ci="2" mb="0" cb="0"/>
<line nr="110" mi="0" ci="5" mb="0" cb="0"/>
<line nr="111" mi="0" ci="2" mb="0" cb="0"/>
<line nr="113" mi="0" ci="1" mb="0" cb="0"/>
<line nr="114" mi="0" ci="7" mb="0" cb="0"/>
<line nr="116" mi="0" ci="3" mb="0" cb="0"/>
<line nr="117" mi="0" ci="12" mb="0" cb="0"/>
<line nr="118" mi="0" ci="26" mb="0" cb="0"/>
<line nr="119" mi="0" ci="10" mb="0" cb="2"/>
<line nr="120" mi="0" ci="7" mb="0" cb="0"/>
<line nr="122" mi="0" ci="3" mb="0" cb="0"/>
<line nr="123" mi="0" ci="9" mb="0" cb="0"/>
<line nr="124" mi="0" ci="14" mb="0" cb="2"/>
<line nr="125" mi="0" ci="2" mb="0" cb="0"/>
<line nr="126" mi="0" ci="3" mb="0" cb="0"/>
<counter type="INSTRUCTION" missed="98" covered="642"/>
<counter type="BRANCH" missed="16" covered="38"/>
<counter type="LINE" missed="6" covered="81"/>
<counter type="COMPLEXITY" missed="15" covered="20"/>
<counter type="METHOD" missed="3" covered="5"/>
<counter type="CLASS" missed="2" covered="2"/>
</sourcefile>
</package>
<package name="com/example/models">
<sourcefile name="Thing.kt">
<line nr="9" mi="0" ci="8" mb="0" cb="0"/>
<line nr="10" mi="4" ci="3" mb="0" cb="0"/>
<line nr="11" mi="3" ci="0" mb="0" cb="0"/>
<line nr="14" mi="0" ci="8" mb="0" cb="2"/>
<line nr="15" mi="9" ci="3" mb="1" cb="1"/>
<line nr="16" mi="0" ci="1" mb="0" cb="0"/>
<line nr="19" mi="0" ci="8" mb="0" cb="0"/>
<line nr="20" mi="4" ci="3" mb="0" cb="0"/>
<line nr="21" mi="3" ci="0" mb="0" cb="0"/>
<line nr="24" mi="5" ci="3" mb="1" cb="1"/>
<line nr="25" mi="9" ci="3" mb="1" cb="1"/>
<line nr="26" mi="0" ci="1" mb="0" cb="0"/>
<line nr="29" mi="0" ci="11" mb="0" cb="0"/>
<line nr="30" mi="0" ci="3" mb="0" cb="0"/>
<line nr="31" mi="4" ci="3" mb="0" cb="0"/>
<line nr="32" mi="4" ci="3" mb="0" cb="0"/>
<line nr="35" mi="0" ci="12" mb="0" cb="2"/>
<line nr="36" mi="0" ci="12" mb="0" cb="2"/>
<line nr="37" mi="0" ci="1" mb="0" cb="0"/>
<line nr="40" mi="0" ci="5" mb="0" cb="0"/>
<line nr="41" mi="4" ci="3" mb="0" cb="0"/>
<line nr="44" mi="9" ci="3" mb="1" cb="1"/>
<line nr="45" mi="0" ci="1" mb="0" cb="0"/>
<line nr="48" mi="0" ci="12" mb="0" cb="0"/>
<line nr="49" mi="0" ci="3" mb="0" cb="0"/>
<line nr="50" mi="0" ci="3" mb="0" cb="0"/>
<line nr="51" mi="3" ci="0" mb="0" cb="0"/>
<counter type="INSTRUCTION" missed="61" covered="119"/>
<counter type="BRANCH" missed="4" covered="10"/>
<counter type="LINE" missed="3" covered="24"/>
<counter type="COMPLEXITY" missed="12" covered="16"/>
<counter type="METHOD" missed="8" covered="13"/>
<counter type="CLASS" missed="0" covered="5"/>
</sourcefile>
</package>
<package name="com/example/generated">
<sourcefile name="Untracked.kt">
<line nr="12" mi="8" ci="0" mb="0" cb="0"/>
<line nr="17" mi="6" ci="0" mb="0" cb="0"/>
<line nr="20" mi="8" ci="0" mb="0" cb="0"/>
<line nr="21" mi="2" ci="0" mb="0" cb="0"/>
<line nr="22" mi="3" ci="0" mb="0" cb="0"/>
<line nr="23" mi="4" ci="0" mb="0" cb="0"/>
<line nr="24" mi="13" ci="0" mb="0" cb="0"/>
<line nr="27" mi="7" ci="0" mb="0" cb="0"/>
<line nr="28" mi="5" ci="0" mb="4" cb="0"/>
<line nr="29" mi="19" ci="0" mb="0" cb="0"/>
<line nr="30" mi="4" ci="0" mb="0" cb="0"/>
<line nr="31" mi="3" ci="0" mb="0" cb="0"/>
<line nr="32" mi="1" ci="0" mb="0" cb="0"/>
<line nr="33" mi="1" ci="0" mb="0" cb="0"/>
<line nr="34" mi="2" ci="0" mb="0" cb="0"/>
<line nr="35" mi="2" ci="0" mb="0" cb="0"/>
<line nr="39" mi="23" ci="0" mb="0" cb="0"/>
<line nr="40" mi="3" ci="0" mb="0" cb="0"/>
<line nr="41" mi="3" ci="0" mb="0" cb="0"/>
<line nr="42" mi="1" ci="0" mb="0" cb="0"/>
<line nr="43" mi="5" ci="0" mb="0" cb="0"/>
<line nr="44" mi="3" ci="0" mb="2" cb="0"/>
<line nr="45" mi="21" ci="0" mb="0" cb="0"/>
<line nr="46" mi="13" ci="0" mb="0" cb="0"/>
<line nr="48" mi="1" ci="0" mb="0" cb="0"/>
<line nr="51" mi="9" ci="0" mb="0" cb="0"/>
<line nr="52" mi="2" ci="0" mb="0" cb="0"/>
<line nr="53" mi="3" ci="0" mb="0" cb="0"/>
<line nr="54" mi="4" ci="0" mb="0" cb="0"/>
<line nr="55" mi="13" ci="0" mb="0" cb="0"/>
<line nr="58" mi="7" ci="0" mb="0" cb="0"/>
<line nr="59" mi="5" ci="0" mb="4" cb="0"/>
<line nr="60" mi="19" ci="0" mb="0" cb="0"/>
<line nr="61" mi="4" ci="0" mb="0" cb="0"/>
<line nr="62" mi="3" ci="0" mb="0" cb="0"/>
<line nr="63" mi="1" ci="0" mb="0" cb="0"/>
<line nr="64" mi="1" ci="0" mb="0" cb="0"/>
<line nr="65" mi="2" ci="0" mb="0" cb="0"/>
<line nr="66" mi="2" ci="0" mb="0" cb="0"/>
<line nr="70" mi="23" ci="0" mb="0" cb="0"/>
<line nr="71" mi="1" ci="0" mb="0" cb="0"/>
<line nr="72" mi="2" ci="0" mb="0" cb="0"/>
<line nr="73" mi="3" ci="0" mb="0" cb="0"/>
<line nr="74" mi="1" ci="0" mb="0" cb="0"/>
<line nr="75" mi="5" ci="0" mb="0" cb="0"/>
<line nr="76" mi="3" ci="0" mb="2" cb="0"/>
<line nr="77" mi="21" ci="0" mb="0" cb="0"/>
<line nr="78" mi="13" ci="0" mb="0" cb="0"/>
<line nr="80" mi="1" ci="0" mb="0" cb="0"/>
<line nr="82" mi="1" ci="0" mb="0" cb="0"/>
<line nr="84" mi="15" ci="0" mb="0" cb="0"/>
<line nr="87" mi="1" ci="5" mb="0" cb="0"/>
<line nr="89" mi="0" ci="5" mb="0" cb="0"/>
<line nr="90" mi="0" ci="6" mb="0" cb="0"/>
<line nr="91" mi="0" ci="8" mb="0" cb="0"/>
<line nr="92" mi="0" ci="8" mb="0" cb="0"/>
<line nr="93" mi="0" ci="8" mb="0" cb="0"/>
<line nr="94" mi="0" ci="8" mb="0" cb="0"/>
<line nr="95" mi="0" ci="8" mb="0" cb="0"/>
<line nr="96" mi="0" ci="8" mb="0" cb="0"/>
<line nr="97" mi="0" ci="8" mb="0" cb="0"/>
<line nr="98" mi="0" ci="8" mb="0" cb="0"/>
<line nr="99" mi="0" ci="8" mb="0" cb="0"/>
<line nr="100" mi="0" ci="9" mb="0" cb="0"/>
<line nr="101" mi="0" ci="8" mb="0" cb="0"/>
<line nr="102" mi="0" ci="8" mb="0" cb="0"/>
<line nr="103" mi="0" ci="7" mb="0" cb="0"/>
<line nr="107" mi="2" ci="8" mb="0" cb="0"/>
<line nr="108" mi="0" ci="8" mb="0" cb="0"/>
<line nr="109" mi="0" ci="8" mb="0" cb="0"/>
<line nr="110" mi="0" ci="3" mb="0" cb="0"/>
<line nr="113" mi="2" ci="9" mb="0" cb="0"/>
<line nr="114" mi="0" ci="8" mb="0" cb="0"/>
<line nr="115" mi="0" ci="8" mb="0" cb="0"/>
<line nr="116" mi="0" ci="8" mb="0" cb="0"/>
<line nr="117" mi="0" ci="8" mb="0" cb="0"/>
<line nr="118" mi="0" ci="8" mb="0" cb="0"/>
<line nr="119" mi="0" ci="8" mb="0" cb="0"/>
<line nr="120" mi="0" ci="3" mb="0" cb="0"/>
<counter type="INSTRUCTION" missed="345" covered="212"/>
<counter type="BRANCH" missed="12" covered="0"/>
<counter type="LINE" missed="51" covered="28"/>
<counter type="COMPLEXITY" missed="19" covered="1"/>
<counter type="METHOD" missed="13" covered="1"/>
<counter type="CLASS" missed="9" covered="1"/>
</sourcefile>
</package>
</report>
@@ -0,0 +1,56 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { parseString } from 'xml2js';
import fs from 'fs';
import { Cobertura } from './cobertura';
import { CoberturaXML } from './types';
import { getVoidLogger } from '@backstage/backend-common';
/* eslint-disable no-restricted-syntax */
describe('convert cobertura', () => {
const converter = new Cobertura(getVoidLogger());
[1, 2, 3, 4, 5].forEach(idx => {
let fixture: CoberturaXML;
parseString(
fs.readFileSync(
`${__dirname}/../__fixtures__/cobertura-testdata-${idx}.xml`,
),
(_e, r) => {
fixture = r;
},
);
const expected = JSON.parse(
fs
.readFileSync(
`${__dirname}/../__fixtures__/cobertura-jsoncoverage-files-${idx}.json`,
)
.toString(),
);
const scmFiles = fs
.readFileSync(
`${__dirname}/../__fixtures__/cobertura-sourcefiles-${idx}.txt`,
)
.toString()
.split('\n');
it(`${idx} converts a cobertura report`, () => {
const files = converter.convert(fixture, scmFiles);
expect(files).toEqual(expected);
});
});
});
@@ -0,0 +1,126 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BranchHit, FileEntry } from '../jsoncoverage-types';
import { CoberturaXML, InnerClass, LineHit } from './types';
import { Logger } from 'winston';
import { Converter } from '.';
export class Cobertura extends Converter {
constructor(readonly logger: Logger) {
super(logger);
}
/**
* convert cobertura into shared json coverage format
*
* @param xml cobertura xml object
* @param scmFiles list of files that are commited to SCM
*/
convert(xml: CoberturaXML, scmFiles: string[]): FileEntry[] {
const ppc = xml.coverage.packages
?.flatMap(p => p.package)
.filter(Boolean)
.flatMap(p => p.classes);
const pc = xml.coverage.package?.filter(Boolean).flatMap(p => p.classes);
const classes = [ppc, pc]
.flat()
.filter(Boolean)
.flatMap(c => c.class)
.filter(Boolean);
const jscov: Array<FileEntry> = [];
classes.forEach(c => {
const packageAndFilename = c.$.filename;
const lines = this.extractLines(c);
const lineHits: Record<number, number> = {};
const branchHits: Record<number, BranchHit> = {};
lines.forEach(l => {
if (!lineHits[l.number]) {
lineHits[l.number] = 0;
}
lineHits[l.number] += l.hits;
if (l.branch && l['condition-coverage']) {
const bh = this.parseBranch(l['condition-coverage']);
if (bh) {
branchHits[l.number] = bh;
}
}
});
const currentFile = scmFiles.find(f => f.endsWith(packageAndFilename));
this.logger.info(`matched ${packageAndFilename} to ${currentFile}`);
if (
scmFiles.length === 0 ||
(Object.keys(lineHits).length > 0 && currentFile)
) {
jscov.push({
filename: currentFile || packageAndFilename,
branchHits: branchHits,
lineHits: lineHits,
});
}
});
return jscov;
}
/**
* Parses branch coverage information from condition-coverage
*
* @param condition condition-coverage value from line coverage
*/
private parseBranch(condition: string): BranchHit | null {
const pattern = /[0-9\.]+\%\s\(([0-9]+)\/([0-9]+)\)/;
const match = condition.match(pattern);
if (!match) {
return null;
}
const covered = parseInt(match[1], 10);
const available = parseInt(match[2], 10);
return {
covered: covered,
missed: available - covered,
available: available,
};
}
/**
* Extract line hits from a class coverage entry
*
* @param clz class coverage information
*/
private extractLines(clz: InnerClass): Array<LineHit> {
const classLines = clz.lines.flatMap(l => l.line);
const methodLines = clz.methods
?.flatMap(m => m.method)
.filter(Boolean)
.flatMap(m => m.lines)
.filter(Boolean)
.flatMap(l => l.line);
const lines = [classLines, methodLines].flat().filter(Boolean);
const lineHits = lines.map(l => {
return {
number: parseInt((l.$.number as unknown) as string, 10),
hits: parseInt((l.$.hits as unknown) as string, 10),
'condition-coverage': l.$['condition-coverage'],
branch: l.$.branch,
};
});
return lineHits;
}
}
@@ -0,0 +1,23 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Logger } from 'winston';
import { FileEntry } from '../jsoncoverage-types';
export abstract class Converter {
constructor(readonly logger: Logger) {}
abstract convert(xml: any, scmFiles: Array<string>): Array<FileEntry>;
}
@@ -0,0 +1,50 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { parseString } from 'xml2js';
import fs from 'fs';
import { Jacoco } from './jacoco';
import { JacocoXML } from './types';
import { getVoidLogger } from '@backstage/backend-common';
/* eslint-disable no-restricted-syntax */
describe('convert jacoco', () => {
const converter = new Jacoco(getVoidLogger());
let fixture: JacocoXML;
parseString(
fs.readFileSync(`${__dirname}/../__fixtures__/jacoco-testdata-1.xml`),
(_e, r) => {
fixture = r;
},
);
const expected = JSON.parse(
fs
.readFileSync(
`${__dirname}/../__fixtures__/jacoco-jsoncoverage-files-1.json`,
)
.toString(),
);
const scmFiles = fs
.readFileSync(`${__dirname}/../__fixtures__/jacoco-sourcefiles-1.txt`)
.toString()
.split('\n');
it('converts a jacoco report', () => {
const files = converter.convert(fixture, scmFiles);
expect(files.sort()).toEqual(expected.sort());
});
});
@@ -0,0 +1,96 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BranchHit, FileEntry } from '../jsoncoverage-types';
import { JacocoSourceFile, JacocoXML } from './types';
import { Logger } from 'winston';
import { Converter } from '.';
type ParsedLine = {
number: number;
missed_instructions: number;
covered_instructions: number;
missed_branches: number;
covered_branches: number;
};
export class Jacoco extends Converter {
constructor(readonly logger: Logger) {
super(logger);
}
/**
* Converts jacoco into shared json coverage format
*
* @param xml jacoco xml object
* @param scmFiles list of files that are commited to SCM
*/
convert(xml: JacocoXML, scmFiles: Array<string>): Array<FileEntry> {
const jscov: Array<FileEntry> = [];
xml.report.package.forEach(r => {
const packageName = r.$.name;
r.sourcefile.forEach(sf => {
const fileName = sf.$.name;
const lines = this.extractLines(sf);
const lineHits: Record<number, number> = {};
const branchHits: Record<number, BranchHit> = {};
lines.forEach(l => {
if (!lineHits[l.number]) {
lineHits[l.number] = 0;
}
lineHits[l.number] += l.covered_instructions;
const ab = l.covered_branches + l.missed_branches;
if (ab > 0) {
branchHits[l.number] = {
covered: l.covered_branches,
missed: l.missed_branches,
available: ab,
};
}
});
const packageAndFilename = `${packageName}/${fileName}`;
const currentFile = scmFiles.find(f => f.endsWith(packageAndFilename));
this.logger.info(`matched ${packageAndFilename} to ${currentFile}`);
if (Object.keys(lineHits).length > 0 && currentFile) {
jscov.push({
filename: currentFile,
branchHits: branchHits,
lineHits: lineHits,
});
}
});
});
return jscov;
}
private extractLines(sourcefile: JacocoSourceFile): ParsedLine[] {
const parsed: ParsedLine[] = [];
sourcefile.line.forEach(l => {
parsed.push({
number: parseInt(l.$.nr, 10),
missed_instructions: parseInt(l.$.mi, 10),
covered_instructions: parseInt(l.$.ci, 10),
missed_branches: parseInt(l.$.mb, 10),
covered_branches: parseInt(l.$.cb, 10),
});
});
return parsed;
}
}
@@ -0,0 +1,92 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// *** Cobertura ***
export type CoberturaXML = {
coverage: Coverage;
};
export type Coverage = {
packages: Array<Package>;
package: Array<InnerPackage>;
};
export type Package = {
package: Array<InnerPackage>;
};
export type InnerPackage = {
classes: Array<Class>;
};
export type Class = {
class: Array<InnerClass>;
};
export type InnerClass = {
$: {
filename: string;
};
lines: Array<Line>;
methods: Array<Method>;
};
export type Method = {
method: Array<InnerMethod>;
};
export type InnerMethod = {
lines: Array<Line>;
};
export type Line = {
line: Array<InnerLine>;
};
export type InnerLine = {
$: LineHit;
};
export type LineHit = {
branch?: boolean;
'condition-coverage'?: string;
number: number;
hits: number;
};
// *** Jacoco ***
export type JacocoXML = {
report: JacocoReport;
};
export type JacocoReport = {
package: JacocoPackage[];
};
export type JacocoPackage = {
$: {
name: string;
};
sourcefile: JacocoSourceFile[];
};
export type JacocoSourceFile = {
$: {
name: string;
};
line: JacocoLine[];
};
export type JacocoLine = {
$: {
nr: string;
mi: string;
ci: string;
mb: string;
cb: string;
};
};
@@ -0,0 +1,71 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { EntityName } from '@backstage/catalog-model';
export type JsonCodeCoverage = {
metadata: CoverageMetadata;
entity: EntityName;
files: Array<FileEntry>;
};
export type JsonCoverageHistory = {
entity: EntityName;
history: Array<CoverageHistory>;
};
export type CoverageHistory = {
line: {
available: number;
covered: number;
};
branch: BranchHit;
};
export type CoverageMetadata = {
vcs: {
type: string;
location: string;
};
generationTime: number;
};
export type BranchHit = {
covered: number;
missed: number;
available: number;
};
export type FileEntry = {
filename: string;
lineHits: Record<number, number>;
branchHits: Record<number, BranchHit>;
};
export type AggregateCoverage = {
timestamp: number;
line: {
available: number;
covered: number;
missed: number;
percentage: number;
};
branch: {
available: number;
covered: number;
missed: number;
percentage: number;
};
};
@@ -0,0 +1,79 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import express from 'express';
import request from 'supertest';
import {
getVoidLogger,
PluginDatabaseManager,
PluginEndpointDiscovery,
SingleConnectionDatabaseManager,
UrlReaders,
} from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import { createRouter } from './router';
function createDatabase(): PluginDatabaseManager {
return SingleConnectionDatabaseManager.fromConfig(
new ConfigReader({
backend: {
database: {
client: 'sqlite3',
connection: ':memory:',
},
},
}),
).forPlugin('code-coverage');
}
const testDiscovery: jest.Mocked<PluginEndpointDiscovery> = {
getBaseUrl: jest
.fn()
.mockResolvedValue('http://localhost:7000/api/code-coverage'),
getExternalBaseUrl: jest.fn(),
};
const mockUrlReader = UrlReaders.default({
logger: getVoidLogger(),
config: new ConfigReader({}),
});
describe('createRouter', () => {
let app: express.Express;
beforeAll(async () => {
const router = await createRouter({
config: new ConfigReader({}),
database: createDatabase(),
discovery: testDiscovery,
urlReader: mockUrlReader,
logger: getVoidLogger(),
});
app = express().use(router);
});
beforeEach(() => {
jest.resetAllMocks();
});
describe('GET /health', () => {
it('returns ok', async () => {
const response = await request(app).get('/health');
expect(response.status).toEqual(200);
expect(response.body).toEqual({ status: 'ok' });
});
});
});
@@ -0,0 +1,229 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import express from 'express';
import Router from 'express-promise-router';
import { Logger } from 'winston';
import xmlparser from 'express-xml-bodyparser';
import { CatalogClient } from '@backstage/catalog-client';
import {
errorHandler,
PluginDatabaseManager,
PluginEndpointDiscovery,
UrlReader,
} from '@backstage/backend-common';
import { InputError, NotFoundError } from '@backstage/errors';
import { Config } from '@backstage/config';
import { ScmIntegrations } from '@backstage/integration';
import { CodeCoverageDatabase } from './CodeCoverageDatabase';
import { aggregateCoverage, CoverageUtils } from './CoverageUtils';
import { Cobertura } from './converter/cobertura';
import { Jacoco } from './converter/jacoco';
import { Converter } from './converter';
export interface RouterOptions {
config: Config;
discovery: PluginEndpointDiscovery;
database: PluginDatabaseManager;
urlReader: UrlReader;
logger: Logger;
}
export interface CodeCoverageApi {
name: string;
}
export const makeRouter = async (
options: RouterOptions,
): Promise<express.Router> => {
const { config, logger, discovery, database, urlReader } = options;
const codeCoverageDatabase = await CodeCoverageDatabase.create(
await database.getClient(),
logger,
);
const codecovUrl = await discovery.getExternalBaseUrl('code-coverage');
const catalogApi = new CatalogClient({ discoveryApi: discovery });
const scm = ScmIntegrations.fromConfig(config);
const router = Router();
router.use(xmlparser());
router.use(express.json());
const utils = new CoverageUtils(scm, urlReader);
router.get('/health', async (_req, res) => {
res.status(200).json({ status: 'ok' });
});
router.get('/:kind/:namespace/:name', async (req, res) => {
const { kind, namespace, name } = req.params;
const entity = await catalogApi.getEntityByName({ kind, namespace, name });
if (!entity) {
throw new NotFoundError(
`No entity found matching ${kind}/${namespace}/${name}`,
);
}
const stored = await codeCoverageDatabase.getCodeCoverage({
kind,
namespace,
name,
});
const aggregate = aggregateCoverage(stored);
res.status(200).json({
...stored,
aggregate: {
line: aggregate.line,
branch: aggregate.branch,
},
});
});
router.get('/:kind/:namespace/:name/history', async (req, res) => {
const { kind, namespace, name } = req.params;
const entity = await catalogApi.getEntityByName({ kind, namespace, name });
if (!entity) {
throw new NotFoundError(
`No entity found matching ${kind}/${namespace}/${name}`,
);
}
const { limit } = req.query;
const history = await codeCoverageDatabase.getHistory(
{
kind,
namespace,
name,
},
parseInt(limit?.toString() || '10', 10),
);
res.status(200).json(history);
});
router.get('/:kind/:namespace/:name/file-content', async (req, res) => {
const { kind, namespace, name } = req.params;
const entity = await catalogApi.getEntityByName({ kind, namespace, name });
if (!entity) {
throw new NotFoundError(
`No entity found matching ${kind}/${namespace}/${name}`,
);
}
const { path } = req.query;
if (!path) {
throw new InputError('Need path query parameter');
}
const sourceLocation =
entity.metadata.annotations?.['backstage.io/source-location'];
if (!sourceLocation) {
throw new InputError(
`No "backstage.io/source-location" annotation on entity ${entity.kind}/${entity.metadata.namespace}/${entity.metadata.name}`,
);
}
const vcs = scm.byUrl(sourceLocation);
if (!vcs) {
throw new InputError(`Unable to determine SCM from ${sourceLocation}`);
}
const scmTree = await urlReader.readTree(sourceLocation);
const scmFile = (await scmTree.files()).find(f => f.path === path);
if (!scmFile) {
res.status(400).json({
message: "couldn't find file in SCM",
file: path,
scm: vcs.title,
});
return;
}
const content = await scmFile?.content();
if (!content) {
res.status(400).json({
message: "couldn't process content of file in SCM",
file: path,
scm: vcs.title,
});
return;
}
const data = content.toString();
res.status(200).contentType('text/plain').send(data);
});
router.post('/:kind/:namespace/:name/', async (req, res) => {
const { kind, namespace, name } = req.params;
const { coverageType } = req.query;
let converter: Converter;
if (!coverageType) {
throw new InputError('Need coverageType query parameter');
} else if (coverageType === 'jacoco') {
converter = new Jacoco(logger);
} else if (coverageType === 'cobertura') {
converter = new Cobertura(logger);
} else {
throw new NotFoundError(`unsupported coverage type '${coverageType}`);
}
const entity = await catalogApi.getEntityByName({ kind, namespace, name });
if (!entity) {
throw new NotFoundError(
`No entity found matching ${kind}/${namespace}/${name}`,
);
}
const {
sourceLocation,
vcs,
scmFiles,
body,
} = await utils.processCoveragePayload(entity, req);
const files = converter.convert(body, scmFiles);
if (!files || files.length === 0) {
throw new InputError(`Unable to parse body as ${coverageType}`);
}
const coverage = await utils.buildCoverage(
entity,
sourceLocation,
vcs,
files,
);
await codeCoverageDatabase.insertCodeCoverage(coverage);
res.status(201).json({
links: [
{
rel: 'coverage',
href: `${codecovUrl}/${kind}/${namespace}/${name}`,
},
],
});
});
router.use(errorHandler());
return router;
};
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const logger = options.logger;
logger.info('Initializing Code Coverage backend');
return makeRouter(options);
}
@@ -0,0 +1,64 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
createServiceBuilder,
loadBackendConfig,
SingleHostDiscovery,
UrlReaders,
useHotMemoize,
} from '@backstage/backend-common';
import { Server } from 'http';
import { Logger } from 'winston';
import { createRouter } from './router';
import { DatabaseManager } from '@backstage/plugin-catalog-backend';
export interface ServerOptions {
port: number;
enableCors: boolean;
logger: Logger;
}
export async function startStandaloneServer(
options: ServerOptions,
): Promise<Server> {
const logger = options.logger.child({ service: 'code-coverage-backend' });
const config = await loadBackendConfig({ logger, argv: process.argv });
const db = useHotMemoize(module, () =>
DatabaseManager.createInMemoryDatabaseConnection(),
);
logger.debug('Starting application server...');
const router = await createRouter({
database: { getClient: () => db },
config,
discovery: SingleHostDiscovery.fromConfig(config),
urlReader: UrlReaders.default({ logger, config }),
logger,
});
const service = createServiceBuilder(module)
.enableCors({ origin: 'http://localhost:3000' })
.addRouter('/code-coverage', router);
return await service.start().catch(err => {
logger.error(err);
process.exit(1);
});
}
module.hot?.accept();
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {};