Skip to content

Commit

Permalink
coverage: provide overall statistics about coverage (#2184)
Browse files Browse the repository at this point in the history
  • Loading branch information
velias authored Feb 15, 2023
1 parent db38f97 commit 1118f36
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 35 deletions.
5 changes: 5 additions & 0 deletions .changeset/neat-pillows-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-inspector/coverage-command': patch
---

coverage: overall statistics about coverage provided
Binary file modified assets/coverage.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
119 changes: 87 additions & 32 deletions example/coverage.json
Original file line number Diff line number Diff line change
@@ -1,71 +1,126 @@
{
"sources": [
{
"body": "query post {\n post {\n ... on Post {\n id\n title\n }\n createdAtSomePoint\n }\n}\n",
"name": "./documents/post.graphql",
"body": "query depthPost {\n post {\n id\n title @client\n author {\n id\n name\n }\n }\n}",
"name": "/home/velias/Devel/projects/github/graphql-inspector/example/documents/depth-post.graphql",
"locationOffset": {
"line": 1,
"column": 1
}
},
{
"body": "query post {\n post {\n ... on Post {\n id\n title\n }\n createdAtSomePoint\n }\n}",
"name": "/home/velias/Devel/projects/github/graphql-inspector/example/documents/post.graphql",
"locationOffset": {
"line": 1,
"column": 1
}
}
],
"types": {
"Query": {
"hits": 1,
"type": "Query",
"children": {
"post": {
"hits": 1,
"locations": {
"./documents/post.graphql": [
{
"start": 15,
"end": 93
}
]
}
},
"posts": {
"hits": 0,
"locations": {}
}
}
},
"Post": {
"hits": 2,
"hits": 4,
"fieldsCount": 4,
"fieldsCountCovered": 2,
"type": "Post",
"children": {
"id": {
"hits": 1,
"hits": 2,
"fieldsCount": 0,
"fieldsCountCovered": 0,
"locations": {
"./documents/post.graphql": [
"/home/velias/Devel/projects/github/graphql-inspector/example/documents/depth-post.graphql": [
{
"start": 31,
"end": 33
}
],
"/home/velias/Devel/projects/github/graphql-inspector/example/documents/post.graphql": [
{
"start": 46,
"end": 48
}
]
}
},
"children": {}
},
"title": {
"hits": 1,
"hits": 2,
"fieldsCount": 0,
"fieldsCountCovered": 0,
"locations": {
"./documents/post.graphql": [
"/home/velias/Devel/projects/github/graphql-inspector/example/documents/depth-post.graphql": [
{
"start": 38,
"end": 51
}
],
"/home/velias/Devel/projects/github/graphql-inspector/example/documents/post.graphql": [
{
"start": 55,
"end": 60
}
]
}
},
"children": {}
},
"createdAt": {
"hits": 0,
"locations": {}
"fieldsCount": 0,
"fieldsCountCovered": 0,
"locations": {},
"children": {}
},
"modifiedAt": {
"hits": 0,
"locations": {}
"fieldsCount": 0,
"fieldsCountCovered": 0,
"locations": {},
"children": {}
}
}
},
"Query": {
"hits": 2,
"fieldsCount": 2,
"fieldsCountCovered": 1,
"type": "Query",
"children": {
"post": {
"hits": 2,
"fieldsCount": 0,
"fieldsCountCovered": 0,
"locations": {
"/home/velias/Devel/projects/github/graphql-inspector/example/documents/depth-post.graphql": [
{
"start": 20,
"end": 94
}
],
"/home/velias/Devel/projects/github/graphql-inspector/example/documents/post.graphql": [
{
"start": 15,
"end": 93
}
]
},
"children": {}
},
"posts": {
"hits": 0,
"fieldsCount": 0,
"fieldsCountCovered": 0,
"locations": {},
"children": {}
}
}
}
},
"stats": {
"numTypes": 2,
"numTypesCoveredFully": 0,
"numTypesCovered": 2,
"numFields": 6,
"numFiledsCovered": 3
}
}
23 changes: 23 additions & 0 deletions packages/commands/coverage/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,29 @@ function renderCoverage(coverage: SchemaCoverage) {
Logger.log(chalk.grey('}\n'));
}
}

Logger.log(
`Types covered: ${
coverage.stats.numTypes > 0
? ((coverage.stats.numTypesCovered / coverage.stats.numTypes) * 100).toFixed(1)
: 'N/A'
}%`,
);
Logger.log(
`Types covered fully: ${
coverage.stats.numTypes > 0
? ((coverage.stats.numTypesCoveredFully / coverage.stats.numTypes) * 100).toFixed(1)
: 'N/A'
}%`,
);
Logger.log(
`Fields covered: ${
coverage.stats.numFields > 0
? ((coverage.stats.numFiledsCovered / coverage.stats.numFields) * 100).toFixed(1)
: 'N/A'
}%`,
);
Logger.log(``);
}

function indent(line: string, space: number): string {
Expand Down
44 changes: 43 additions & 1 deletion packages/core/__tests__/coverage/coverage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('coverage', () => {
type Query {
post: Post
posts: [Post!]
objectById(id: ID!): Identifiable
objectById(id: ID!, unused: String): Identifiable
}
type Mutation {
Expand Down Expand Up @@ -66,6 +66,7 @@ describe('coverage', () => {
expect(results.types.Query.children.post.hits).toEqual(1);
expect(results.types.Query.children.objectById.hits).toEqual(1);
expect(results.types.Query.children.objectById.children.id.hits).toEqual(1);
expect(results.types.Query.children.objectById.children.unused.hits).toEqual(0);
// Post
expect(results.types.Post.hits).toEqual(5);
expect(results.types.Post.children.id.hits).toEqual(2);
Expand All @@ -81,6 +82,47 @@ describe('coverage', () => {
expect(results.types.Mutation.children.submitPost.hits).toEqual(1);
expect(results.types.Mutation.children.submitPost.children.title.hits).toEqual(1);
expect(results.types.Mutation.children.submitPost.children.author.hits).toEqual(1);

// stats
expect(results.stats.numTypes).toEqual(4);
expect(results.stats.numTypesCovered).toEqual(4);
expect(results.stats.numTypesCoveredFully).toEqual(1);
expect(results.stats.numFields).toEqual(14);
expect(results.stats.numFiledsCovered).toEqual(10);
});

test('no coverage', () => {
const results = coverage(schema, []);

// Query
expect(results.types.Query.hits).toEqual(0);
expect(results.types.Query.children.posts.hits).toEqual(0);
expect(results.types.Query.children.post.hits).toEqual(0);
expect(results.types.Query.children.objectById.hits).toEqual(0);
expect(results.types.Query.children.objectById.children.id.hits).toEqual(0);
expect(results.types.Query.children.objectById.children.unused.hits).toEqual(0);
// Post
expect(results.types.Post.hits).toEqual(0);
expect(results.types.Post.children.id.hits).toEqual(0);
expect(results.types.Post.children.title.hits).toEqual(0);
expect(results.types.Post.children.author.hits).toEqual(0);
expect(results.types.Post.children.createdAt.hits).toEqual(0);
// Identifiable
expect(results.types.Identifiable.hits).toEqual(0);
expect(results.types.Identifiable.children.id.hits).toEqual(0);
expect(results.types.Identifiable.children.createdAt.hits).toEqual(0);
// Mutation
expect(results.types.Mutation.hits).toEqual(0);
expect(results.types.Mutation.children.submitPost.hits).toEqual(0);
expect(results.types.Mutation.children.submitPost.children.title.hits).toEqual(0);
expect(results.types.Mutation.children.submitPost.children.author.hits).toEqual(0);

// stats
expect(results.stats.numTypes).toEqual(4);
expect(results.stats.numTypesCovered).toEqual(0);
expect(results.stats.numTypesCoveredFully).toEqual(0);
expect(results.stats.numFields).toEqual(14);
expect(results.stats.numFiledsCovered).toEqual(0);
});

test('introspection', () => {
Expand Down
57 changes: 57 additions & 0 deletions packages/core/src/coverage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,17 @@ export interface Location {

export interface ArgumentCoverage {
hits: number;
fieldsCount: number;
fieldsCountCovered: number;
locations: {
[name: string]: Array<Location>;
};
}

export interface TypeChildCoverage {
hits: number;
fieldsCount: number;
fieldsCountCovered: number;
locations: {
[name: string]: Array<Location>;
};
Expand All @@ -38,6 +42,8 @@ export interface TypeChildCoverage {

export interface TypeCoverage {
hits: number;
fieldsCount: number;
fieldsCountCovered: number;
type: GraphQLNamedType;
children: {
[name: string]: TypeChildCoverage;
Expand All @@ -49,6 +55,13 @@ export interface SchemaCoverage {
types: {
[typename: string]: TypeCoverage;
};
stats: {
numTypes: number;
numTypesCoveredFully: number;
numTypesCovered: number;
numFields: number;
numFiledsCovered: number;
};
}

export interface InvalidDocument {
Expand All @@ -60,6 +73,13 @@ export function coverage(schema: GraphQLSchema, sources: Source[]): SchemaCovera
const coverage: SchemaCoverage = {
sources,
types: {},
stats: {
numTypes: 0,
numTypesCoveredFully: 0,
numTypesCovered: 0,
numFields: 0,
numFiledsCovered: 0,
},
};
const typeMap = schema.getTypeMap();
const typeInfo = new TypeInfo(schema);
Expand Down Expand Up @@ -112,6 +132,8 @@ export function coverage(schema: GraphQLSchema, sources: Source[]): SchemaCovera
if (isObjectType(type) || isInterfaceType(type)) {
const typeCoverage: TypeCoverage = {
hits: 0,
fieldsCount: 0,
fieldsCountCovered: 0,
type,
children: {},
};
Expand All @@ -122,13 +144,17 @@ export function coverage(schema: GraphQLSchema, sources: Source[]): SchemaCovera

typeCoverage.children[field.name] = {
hits: 0,
fieldsCount: 0,
fieldsCountCovered: 0,
locations: {},
children: {},
};

for (const arg of field.args) {
typeCoverage.children[field.name].children[arg.name] = {
hits: 0,
fieldsCount: 0,
fieldsCountCovered: 0,
locations: {},
};
}
Expand All @@ -151,5 +177,36 @@ export function coverage(schema: GraphQLSchema, sources: Source[]): SchemaCovera
});
});

for (const key in coverage.types) {
const me = coverage.types[key];
processStats(me);

coverage.stats.numTypes++;
if (me.fieldsCountCovered > 0) coverage.stats.numTypesCovered++;
if (me.fieldsCount == me.fieldsCountCovered) coverage.stats.numTypesCoveredFully++;
coverage.stats.numFields += me.fieldsCount;
coverage.stats.numFiledsCovered += me.fieldsCountCovered;
}

return coverage;
}

function processStats(me: TypeCoverage | TypeChildCoverage) {
const children = me.children;
if (children) {
for (const k in children) {
const ch = children[k];

if ((ch as TypeChildCoverage).children !== undefined) {
processStats(ch as TypeChildCoverage);
me.fieldsCount += ch.fieldsCount;
me.fieldsCountCovered += ch.fieldsCountCovered;
}

me.fieldsCount++;
if (ch.hits > 0) {
me.fieldsCountCovered++;
}
}
}
}
Binary file modified website/public/assets/img/cli/coverage.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions website/src/pages/docs/essentials/coverage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ graphql-inspector coverage DOCUMENTS SCHEMA

### Output

Depending on enabled flags, a printed GraphQL Schema with stats per each field and a JSON file with
data.
Depending on enabled flags, a printed GraphQL Schema with stats per each field and overall stats, a
JSON file with data.

0 comments on commit 1118f36

Please sign in to comment.