Skip to content

Commit

Permalink
feat: add new functionality to create a graph from unchanged packages
Browse files Browse the repository at this point in the history
This adds a function that creates a subgraph of only new
and changed package nodes in a graph.
  • Loading branch information
alexandru-dragomir committed Mar 29, 2023
1 parent eb98b7a commit a0d5ba7
Show file tree
Hide file tree
Showing 50 changed files with 2,899 additions and 1 deletion.
97 changes: 97 additions & 0 deletions src/core/create-changed-packages-graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { DepGraph, DepGraphInternal, NodeInfo, PkgInfo } from './types';
import { DepGraphImpl } from './dep-graph';
import { DepGraphBuilder } from './builder';
import { eventLoopSpinner } from 'event-loop-spinner';

type NodeId = string;

/**
* Creates an induced subgraph of {@param graphB} with only packages
* that are not present in {@param graphA} or have a different version.
*
* @param graphA
* @param graphB
*/
export async function createChangedPackagesGraph(
graphA: DepGraph,
graphB: DepGraph,
): Promise<DepGraph> {
const depGraph = graphB as DepGraphInternal;

const graphAPackageIds = new Set(
graphA.getDepPkgs().map(DepGraphImpl.getPkgId),
);

const addedOrUpdatedPackages: PkgInfo[] = depGraph
.getDepPkgs()
.filter((pkg) => !graphAPackageIds.has(DepGraphImpl.getPkgId(pkg)));

const depGraphBuilder = new DepGraphBuilder(
depGraph.pkgManager,
depGraph.rootPkg,
);

const parentQueue: [parentId: NodeId, nodeId: NodeId][] = [];
for (const changedPackage of addedOrUpdatedPackages) {
for (const changedNodeId of depGraph.getPkgNodeIds(changedPackage)) {
//we add all nodes with new and changed packages to the new graph.
//a newly added node will also have its dependencies added here, since they are "new".
depGraphBuilder.addPkgNode(
depGraph.getNodePkg(changedNodeId),
changedNodeId,
getNodeInfo(depGraph, changedNodeId),
);

//Push all direct parents of the changed nodes to a queue to later build up a path to root from that node
for (const parentId of depGraph.getNodeParentsNodeIds(changedNodeId)) {
parentQueue.push([parentId, changedNodeId]);

if (eventLoopSpinner.isStarving()) {
await eventLoopSpinner.spin();
}
}
}
}

//add direct and transitive parents for the changed nodes
const visited = new Set([depGraph.rootNodeId]);

while (parentQueue.length > 0) {
const [nodeId, dependencyNodeId] = parentQueue.pop()!;
if (visited.has(nodeId)) {
//ensure we link parents even if visited through another path
depGraphBuilder.connectDep(nodeId, dependencyNodeId);
continue;
}

visited.add(nodeId);

depGraphBuilder.addPkgNode(
depGraph.getNodePkg(nodeId),
nodeId,
getNodeInfo(depGraph, nodeId),
);
depGraphBuilder.connectDep(nodeId, dependencyNodeId);

for (const parentId of depGraph.getNodeParentsNodeIds(nodeId)) {
parentQueue.push([parentId, nodeId]);

if (eventLoopSpinner.isStarving()) {
await eventLoopSpinner.spin();
}
}
}

return depGraphBuilder.build();
}

function getNodeInfo(
depGraph: DepGraphInternal,
nodeId: string,
): NodeInfo | undefined {
const nodeInfo: NodeInfo = depGraph.getNode(nodeId);
if (!nodeInfo || Object.keys(nodeInfo).length === 0) {
return undefined;
}
return nodeInfo;
}
2 changes: 1 addition & 1 deletion src/core/dep-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ class DepGraphImpl implements types.DepGraphInternal {
compareRoot: boolean,
traversedPairs = new Set<string>(),
): boolean {
// Skip root nodes comparision if needed.
// Skip root nodes comparison if needed.
if (
compareRoot ||
(nodeIdA !== graphA.rootNodeId && nodeIdB !== graphB.rootNodeId)
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ export {
} from './core/types';
export { createFromJSON } from './core/create-from-json';
export { DepGraphBuilder } from './core/builder';
export { createChangedPackagesGraph } from './core/create-changed-packages-graph';

import * as Errors from './core/errors';

export { Errors };

import * as legacy from './legacy';

export { legacy };
54 changes: 54 additions & 0 deletions test/core/create-changed-packages-graph.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as depGraphLib from '../../src';
import * as helpers from '../helpers';
import { createChangedPackagesGraph } from '../../src';

describe('filter-unchanged-packages', () => {
it.each`
fixture
${'equals/simple.json'}
${'cyclic-complex-dep-graph.json'}
${'goof-graph.json'}
`(
'result and $fixture are equals for empty initial graph',
async ({ fixture }) => {
const graphB = depGraphLib.createFromJSON(helpers.loadFixture(fixture));

const graphA = new depGraphLib.DepGraphBuilder(
graphB.pkgManager,
graphB.rootPkg,
).build();

const result = await createChangedPackagesGraph(graphA, graphB);
expect(graphB.equals(result)).toBe(true);
},
);

it.each`
fixtureA | fixtureB | expected
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-direct-dep-added.json'} | ${'changed-packages-graph/graph-direct-dep-added-expected.json'}
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-direct-dep-changed-cycle.json'} | ${'changed-packages-graph/graph-direct-dep-changed-cycle-expected.json'}
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-direct-dep-changed.json'} | ${'changed-packages-graph/graph-direct-dep-changed-expected.json'}
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-direct-dep-removed.json'} | ${'changed-packages-graph/graph-direct-dep-removed-expected.json'}
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-direct-dep-with-exiting-transitive-dep-added.json'} | ${'changed-packages-graph/graph-direct-dep-with-exiting-transitive-dep-added-expected.json'}
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-root-and-direct-dep-changed.json'} | ${'changed-packages-graph/graph-root-and-direct-dep-changed-expected.json'}
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-root-changed-expected.json'} | ${'changed-packages-graph/graph-root-changed-expected.json'}
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-transitive-dep-as-direct-dep.json'} | ${'changed-packages-graph/graph-transitive-dep-as-direct-dep-expected.json'}
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-transitive-dep-changed-cycle.json'} | ${'changed-packages-graph/graph-transitive-dep-changed-cycle-expected.json'}
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-transitive-dep-changed.json'} | ${'changed-packages-graph/graph-transitive-dep-changed-expected.json'}
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-transitive-dep-removed.json'} | ${'changed-packages-graph/graph-transitive-dep-removed-expected.json'}
`(
'result is $expected for $fixtureA and $fixtureB',
async ({ fixtureA, fixtureB, expected }) => {
const graphA = depGraphLib.createFromJSON(helpers.loadFixture(fixtureA));

const graphB = depGraphLib.createFromJSON(helpers.loadFixture(fixtureB));

const expectedResult = depGraphLib.createFromJSON(
helpers.loadFixture(expected),
);

const result = await createChangedPackagesGraph(graphA, graphB);
expect(expectedResult.equals(result, { compareRoot: true })).toBe(true);
},
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"schemaVersion": "1.3.0",
"pkgManager": {
"name": "pip"
},
"pkgs": [
{
"id": "a@1",
"info": {
"name": "a",
"version": "1"
}
},
{
"id": "[email protected]",
"info": {
"name": "l",
"version": "0.1"
}
},
{
"id": "[email protected]",
"info": {
"name": "m",
"version": "1.2"
}
}
],
"graph": {
"rootNodeId": "root-node",
"nodes": [
{
"nodeId": "12",
"pkgId": "[email protected]",
"deps": [
{
"nodeId": "13"
}
]
},
{
"nodeId": "13",
"pkgId": "[email protected]",
"deps": []
},
{
"nodeId": "root-node",
"pkgId": "a@1",
"deps": [
{
"nodeId": "12"
}
]
}
]
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit a0d5ba7

Please sign in to comment.