UNPKG

cypress-xray-plugin

Version:

A Cypress plugin for uploading test results to Xray (test management for Jira)

137 lines (136 loc) 5.32 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExecutableGraph = void 0; const command_1 = require("../../hooks/command"); const graph_1 = require("./graph"); var VertexState; (function (VertexState) { /** * The vertex has been queued and is pending computation. */ VertexState[VertexState["PENDING"] = 0] = "PENDING"; /** * The vertex successfully computed its result. */ VertexState[VertexState["COMPUTED"] = 1] = "COMPUTED"; /** * The vertex failed computing its result. */ VertexState[VertexState["FORBIDDEN"] = 2] = "FORBIDDEN"; })(VertexState || (VertexState = {})); /** * Models a graph which can be executed in a top-down fashion, i.e. starting at vertices without * incoming edges and progressing towards leaf vertices without outgoing edges. */ class ExecutableGraph extends graph_1.SimpleDirectedGraph { constructor() { super(...arguments); /** * Maps vertices to their execution states. */ this.states = new Map(); /** * Stores edges which are optional, i.e. that the destination vertex should still compute even * if the predecessor failed to compute its result. */ this.optionalEdges = new Set(); } /** * Triggers the graph's execution. */ async execute() { const roots = [...this.getVertices()].filter((vertex) => !this.hasIncoming(vertex)); await Promise.all(roots.map((root) => { this.states.set(root, VertexState.PENDING); return this.executeFollowedBySuccessors(root); })); } /** * Connects two vertices in the graph with an edge. If the graph does not yet contain the * vertices, they will automatically be inserted prior to the connection. * * @param source - the source vertex * @param destination - the destination vertex * @param optional - `true` to mark the edge optional, `false` otherwise * @returns the new edge * @throws if the connection would introduce a duplicate edge or a cycle */ connect(source, destination, optional = false) { const edge = super.connect(source, destination); if (optional) { this.optionalEdges.add(edge); } return edge; } /** * Returns whether an edge is an optional edge. * * @param edge - the edge * @returns `true` if the edge is optional, `false` otherwise */ isOptional(edge) { return this.optionalEdges.has(edge); } async executeFollowedBySuccessors(vertex) { const successors = new Set(this.getSuccessors(vertex)); try { await vertex.compute(); this.states.set(vertex, VertexState.COMPUTED); // Errors are logged by the computables themselves. // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { const nextRoots = this.markForbidden(vertex); nextRoots.forEach((root) => successors.add(root)); } finally { const successorPromises = []; for (const successor of successors) { if (this.canQueueVertex(successor)) { this.states.set(successor, VertexState.PENDING); successorPromises.push(this.executeFollowedBySuccessors(successor)); } } await Promise.all(successorPromises); } } markForbidden(vertex) { // It might be possible to encounter a vertex deep down the tree which has all inputs marked // optional and could thus still be executed. These need to be collected. const nextRoots = new Set(); if (this.states.get(vertex) !== VertexState.FORBIDDEN) { this.states.set(vertex, VertexState.FORBIDDEN); for (const edge of this.getOutgoing(vertex)) { // All non-optional destinations must also be marked forbidden since they now need // to be skipped entirely. if (!this.optionalEdges.has(edge)) { this.markForbidden(edge.getDestination()).forEach((root) => nextRoots.add(root)); edge.getDestination().setState(command_1.ComputableState.SKIPPED); } else { nextRoots.add(edge.getDestination()); } } } return nextRoots; } canQueueVertex(vertex) { if (this.states.has(vertex)) { return false; } // Only allow computation when all predecessors are done. // Otherwise we might end up skipping in line. // A precedessor edge is: // - ok iff: success || (failed && optional) // - not okay iff: !success && !(failed && optional) for (const incomingEdge of this.getIncoming(vertex)) { if (this.states.get(incomingEdge.getSource()) !== VertexState.COMPUTED && !(this.states.get(incomingEdge.getSource()) === VertexState.FORBIDDEN && this.optionalEdges.has(incomingEdge))) { return false; } } return true; } } exports.ExecutableGraph = ExecutableGraph;