Skip to content

Commit

Permalink
feat: show blank node diffs
Browse files Browse the repository at this point in the history
  • Loading branch information
jeswr committed Jan 3, 2023
1 parent 0a78be8 commit 4f678bd
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 13 deletions.
69 changes: 62 additions & 7 deletions lib/matchers/toBeRdfIsomorphic.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,55 @@
import {isomorphic} from "rdf-isomorphic";
import * as RDF from "@rdfjs/types";
import {quadToStringQuad} from "rdf-string";
import { getGraphBlankNodes, getQuadsWithBlankNodes, hashTerms, isomorphic, ITermHash, uniqGraph } from "rdf-isomorphic";
import { quadToStringQuad } from "rdf-string";
import { everyTerms, someTerms } from 'rdf-terms';

function getNonBlankDiff<Q extends RDF.BaseQuad = RDF.Quad>(a1: Q[], a2: Q[]) {
return a1.filter(
quad =>
everyTerms(quad, term => term.termType !== 'BlankNode')
&& a2.every(q2 => !q2.equals(quad))
)
}


export function getDiff(hash1: ITermHash, hash2: ITermHash) {
const diffed: string[] = [];
const values = new Set(Object.values(hash2));
for (const key in hash1) {
if (!values.has(hash1[key])) {
diffed.push(key);
}
}
return diffed;
}

export function unGroundHashes<Q extends RDF.BaseQuad = RDF.Quad>(graph: Q[]) {
return hashTerms(uniqGraph(getQuadsWithBlankNodes(graph)), getGraphBlankNodes(graph), {})[1]
}

export function getBnodeDiff<Q extends RDF.BaseQuad = RDF.Quad>(receivedQuads: Q[], expectedQuads: Q[]) {

// Hash every term based on the signature of the quads if appears in.
const ungroundedHashesA = unGroundHashes(receivedQuads);
const ungroundedHashesB = unGroundHashes(expectedQuads);
const blankA = uniqGraph(getQuadsWithBlankNodes(receivedQuads));
const blankB = uniqGraph(getQuadsWithBlankNodes(expectedQuads))

const received: Record<string, Q[]> = {}
const expected: Record<string, Q[]> = {}

for (const elem of getDiff(ungroundedHashesA, ungroundedHashesB)) {
received[elem] = blankA.filter(quad => someTerms(quad, (term) => term.termType === 'BlankNode' && term.value === elem.slice(2)));
}

for (const elem of getDiff(ungroundedHashesB, ungroundedHashesA)) {
expected[elem] = blankB.filter(quad => someTerms(quad, (term) => term.termType === 'BlankNode' && term.value === elem.slice(2)));
}

return {
received, expected
}
}

function quadArrayToString<Q extends RDF.BaseQuad = RDF.Quad>(quadArray: Q[]): string {
return '[\n' + quadArray.map((quad) => ' ' + JSON.stringify(quadToStringQuad(quad))).join(',\n') + '\n]';
Expand All @@ -12,6 +61,8 @@ export default {
const actualArray = [...actual];

if (!isomorphic(receivedArray, actualArray)) {
const { received: receivedBnodes, expected: actualBnodes } = getBnodeDiff(receivedArray, actualArray)

return {
message: () => `expected two graphs to be isomorphic.
Expand All @@ -21,13 +72,17 @@ ${quadArrayToString(actualArray)}
Actual:
${quadArrayToString(receivedArray)}
Missing:
${quadArrayToString(actualArray.filter(quad => receivedArray.every(q2 => !q2.equals(quad))))}
Missing Quads (that don't contain Blank Nodes):
${quadArrayToString(getNonBlankDiff(actualArray, receivedArray))}
Additional Quads (that don't contain Blank Nodes):
${quadArrayToString(getNonBlankDiff(receivedArray, actualArray))}
Additional:
${quadArrayToString(receivedArray.filter(quad => actualArray.every(q2 => !q2.equals(quad))))}
Missing Blank Node Patterns:
${Object.entries(actualBnodes).map(([bnode, quads]) => bnode + ' : ' + quadArrayToString(quads)).join('\n')}
**Note** The missing and additional arrays may contain extra quads as they do not account for isomorphisms in blank nodes
Additional Blank Node Patterns:
${Object.entries(receivedBnodes).map(([bnode, quads]) => bnode + ' : ' + quadArrayToString(quads)).join('\n')}
`,
pass: false,
};
Expand Down
100 changes: 94 additions & 6 deletions test/matchers/__snapshots__/toBeRdfIsomorphic-test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,22 @@ exports[`#toBeRdfIsomorphic should fail for quad arrays with different length 1`
{\\"subject\\":\\"s3\\",\\"predicate\\":\\"p3\\",\\"object\\":\\"o3\\",\\"graph\\":\\"g3\\"}
]
Missing:
Missing Quads (that don't contain Blank Nodes):
[
]
Additional:
Additional Quads (that don't contain Blank Nodes):
[
{\\"subject\\":\\"s2\\",\\"predicate\\":\\"p2\\",\\"object\\":\\"o2\\",\\"graph\\":\\"g2\\"},
{\\"subject\\":\\"s3\\",\\"predicate\\":\\"p3\\",\\"object\\":\\"o3\\",\\"graph\\":\\"g3\\"}
]
**Note** The missing and additional arrays may contain extra quads as they do not account for isomorphisms in blank nodes
Missing Blank Node Patterns:
Additional Blank Node Patterns:
"
`;

Expand All @@ -47,18 +51,22 @@ exports[`#toBeRdfIsomorphic should fail for quad arrays with equal length but di
{\\"subject\\":\\"s3\\",\\"predicate\\":\\"p3\\",\\"object\\":\\"o3\\",\\"graph\\":\\"g3\\"}
]
Missing:
Missing Quads (that don't contain Blank Nodes):
[
]
Additional:
Additional Quads (that don't contain Blank Nodes):
[
{\\"subject\\":\\"s2\\",\\"predicate\\":\\"p2\\",\\"object\\":\\"o2\\",\\"graph\\":\\"g2\\"},
{\\"subject\\":\\"s3\\",\\"predicate\\":\\"p3\\",\\"object\\":\\"o3\\",\\"graph\\":\\"g3\\"}
]
**Note** The missing and additional arrays may contain extra quads as they do not account for isomorphisms in blank nodes
Missing Blank Node Patterns:
Additional Blank Node Patterns:
"
`;

Expand All @@ -80,3 +88,83 @@ exports[`#toBeRdfIsomorphic should not fail for equal quad arrays 1`] = `
]
"
`;

exports[`#toBeRdfIsomorphic should not succeed for quad arrays with equal length but different contents (bnodes isomorphic) 1`] = `
"expected two graphs to be isomorphic.
Expected:
[
{\\"subject\\":\\"_:s1\\",\\"predicate\\":\\"p1\\",\\"object\\":\\"o1\\",\\"graph\\":\\"g1\\"},
{\\"subject\\":\\"s1\\",\\"predicate\\":\\"p1\\",\\"object\\":\\"o1\\",\\"graph\\":\\"g1\\"},
{\\"subject\\":\\"s1\\",\\"predicate\\":\\"p1\\",\\"object\\":\\"o1\\",\\"graph\\":\\"g1\\"},
{\\"subject\\":\\"s1\\",\\"predicate\\":\\"p1\\",\\"object\\":\\"o1\\",\\"graph\\":\\"g1\\"}
]
Actual:
[
{\\"subject\\":\\"_:s1\\",\\"predicate\\":\\"p1\\",\\"object\\":\\"o1\\",\\"graph\\":\\"g1\\"},
{\\"subject\\":\\"s1\\",\\"predicate\\":\\"p1\\",\\"object\\":\\"o1\\",\\"graph\\":\\"g1\\"},
{\\"subject\\":\\"s2\\",\\"predicate\\":\\"p2\\",\\"object\\":\\"o2\\",\\"graph\\":\\"g2\\"},
{\\"subject\\":\\"s3\\",\\"predicate\\":\\"p3\\",\\"object\\":\\"o3\\",\\"graph\\":\\"g3\\"}
]
Missing Quads (that don't contain Blank Nodes):
[
]
Additional Quads (that don't contain Blank Nodes):
[
{\\"subject\\":\\"s2\\",\\"predicate\\":\\"p2\\",\\"object\\":\\"o2\\",\\"graph\\":\\"g2\\"},
{\\"subject\\":\\"s3\\",\\"predicate\\":\\"p3\\",\\"object\\":\\"o3\\",\\"graph\\":\\"g3\\"}
]
Missing Blank Node Patterns:
Additional Blank Node Patterns:
"
`;

exports[`#toBeRdfIsomorphic should not succeed for quad arrays with equal length but different contents (bnodes non-isomorphic) 1`] = `
"expected two graphs to be isomorphic.
Expected:
[
{\\"subject\\":\\"_:s1\\",\\"predicate\\":\\"p1\\",\\"object\\":\\"o2\\",\\"graph\\":\\"g1\\"},
{\\"subject\\":\\"s1\\",\\"predicate\\":\\"p1\\",\\"object\\":\\"o1\\",\\"graph\\":\\"g1\\"},
{\\"subject\\":\\"s1\\",\\"predicate\\":\\"p1\\",\\"object\\":\\"o1\\",\\"graph\\":\\"g1\\"},
{\\"subject\\":\\"s1\\",\\"predicate\\":\\"p1\\",\\"object\\":\\"o1\\",\\"graph\\":\\"g1\\"}
]
Actual:
[
{\\"subject\\":\\"_:s1\\",\\"predicate\\":\\"p1\\",\\"object\\":\\"o1\\",\\"graph\\":\\"g1\\"},
{\\"subject\\":\\"s1\\",\\"predicate\\":\\"p1\\",\\"object\\":\\"o1\\",\\"graph\\":\\"g1\\"},
{\\"subject\\":\\"s2\\",\\"predicate\\":\\"p2\\",\\"object\\":\\"o2\\",\\"graph\\":\\"g2\\"},
{\\"subject\\":\\"s3\\",\\"predicate\\":\\"p3\\",\\"object\\":\\"o3\\",\\"graph\\":\\"g3\\"}
]
Missing Quads (that don't contain Blank Nodes):
[
]
Additional Quads (that don't contain Blank Nodes):
[
{\\"subject\\":\\"s2\\",\\"predicate\\":\\"p2\\",\\"object\\":\\"o2\\",\\"graph\\":\\"g2\\"},
{\\"subject\\":\\"s3\\",\\"predicate\\":\\"p3\\",\\"object\\":\\"o3\\",\\"graph\\":\\"g3\\"}
]
Missing Blank Node Patterns:
_:s1 : [
{\\"subject\\":\\"_:s1\\",\\"predicate\\":\\"p1\\",\\"object\\":\\"o2\\",\\"graph\\":\\"g1\\"}
]
Additional Blank Node Patterns:
_:s1 : [
{\\"subject\\":\\"_:s1\\",\\"predicate\\":\\"p1\\",\\"object\\":\\"o1\\",\\"graph\\":\\"g1\\"}
]
"
`;
108 changes: 108 additions & 0 deletions test/matchers/toBeRdfIsomorphic-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,114 @@ describe('#toBeRdfIsomorphic', () => {
]);
});

it('should not succeed for quad arrays with equal length but different contents (bnodes isomorphic)', () => {
return expect(() => expect([
DF.quad(
DF.blankNode('s1'),
DF.namedNode('p1'),
DF.namedNode('o1'),
DF.namedNode('g1'),
),
DF.quad(
DF.namedNode('s1'),
DF.namedNode('p1'),
DF.namedNode('o1'),
DF.namedNode('g1'),
),
DF.quad(
DF.namedNode('s2'),
DF.namedNode('p2'),
DF.namedNode('o2'),
DF.namedNode('g2'),
),
DF.quad(
DF.namedNode('s3'),
DF.namedNode('p3'),
DF.namedNode('o3'),
DF.namedNode('g3'),
),
]).toBeRdfIsomorphic([
DF.quad(
DF.blankNode('s1'),
DF.namedNode('p1'),
DF.namedNode('o1'),
DF.namedNode('g1'),
),
DF.quad(
DF.namedNode('s1'),
DF.namedNode('p1'),
DF.namedNode('o1'),
DF.namedNode('g1'),
),
DF.quad(
DF.namedNode('s1'),
DF.namedNode('p1'),
DF.namedNode('o1'),
DF.namedNode('g1'),
),
DF.quad(
DF.namedNode('s1'),
DF.namedNode('p1'),
DF.namedNode('o1'),
DF.namedNode('g1'),
),
])).toThrowErrorMatchingSnapshot();
});

it('should not succeed for quad arrays with equal length but different contents (bnodes non-isomorphic)', () => {
return expect(() => expect([
DF.quad(
DF.blankNode('s1'),
DF.namedNode('p1'),
DF.namedNode('o1'),
DF.namedNode('g1'),
),
DF.quad(
DF.namedNode('s1'),
DF.namedNode('p1'),
DF.namedNode('o1'),
DF.namedNode('g1'),
),
DF.quad(
DF.namedNode('s2'),
DF.namedNode('p2'),
DF.namedNode('o2'),
DF.namedNode('g2'),
),
DF.quad(
DF.namedNode('s3'),
DF.namedNode('p3'),
DF.namedNode('o3'),
DF.namedNode('g3'),
),
]).toBeRdfIsomorphic([
DF.quad(
DF.blankNode('s1'),
DF.namedNode('p1'),
DF.namedNode('o2'),
DF.namedNode('g1'),
),
DF.quad(
DF.namedNode('s1'),
DF.namedNode('p1'),
DF.namedNode('o1'),
DF.namedNode('g1'),
),
DF.quad(
DF.namedNode('s1'),
DF.namedNode('p1'),
DF.namedNode('o1'),
DF.namedNode('g1'),
),
DF.quad(
DF.namedNode('s1'),
DF.namedNode('p1'),
DF.namedNode('o1'),
DF.namedNode('g1'),
),
])).toThrowErrorMatchingSnapshot();
});

it('should not succeed for quad arrays with nested quads with equal length but different contents', () => {
return expect([
DF.quad(
Expand Down

0 comments on commit 4f678bd

Please sign in to comment.