-
Notifications
You must be signed in to change notification settings - Fork 2.5k
fix/902 --bail flag not stopping execution when a test fails #8103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
90dc2c4
a44db21
f101266
23e9221
a84c065
3272c05
203b89b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,17 +4,17 @@ const path = require('path'); | |
| const yaml = require('js-yaml'); | ||
| const { forOwn, cloneDeep } = require('lodash'); | ||
| const { getRunnerSummary } = require('@usebruno/common/runner'); | ||
| const { exists, isFile, isDirectory } = require('../utils/filesystem'); | ||
| const { exists, isFile, isDirectory, stripExtension } = require('../utils/filesystem'); | ||
| const { runSingleRequest } = require('../runner/run-single-request'); | ||
| const { getEnvVars } = require('../utils/bru'); | ||
| const { parseEnvironmentJson } = require('../utils/environment'); | ||
| const { isRequestTagsIncluded } = require('@usebruno/common'); | ||
| const makeJUnitOutput = require('../reporters/junit'); | ||
| const makeHtmlOutput = require('../reporters/html'); | ||
| const { rpad } = require('../utils/common'); | ||
| const { getOptions } = require('../utils/bru'); | ||
| const { parseDotEnv, parseEnvironment } = require('@usebruno/filestore'); | ||
| const constants = require('../constants'); | ||
| const Table = require('cli-table3'); | ||
| const { findItemInCollection, createCollectionJsonFromPathname, getCallStack, FORMAT_CONFIG } = require('../utils/collection'); | ||
| const { hasExecutableTestInScript } = require('../utils/request'); | ||
| const { createSkippedFileResults } = require('../utils/run'); | ||
|
|
@@ -23,86 +23,64 @@ const { getSystemProxy } = require('@usebruno/requests'); | |
| const command = 'run [paths...]'; | ||
| const desc = 'Run one or more requests/folders'; | ||
|
|
||
| const formatTestSummary = (label, maxLength, passed, failed, total, errorCount = 0, skippedCount = 0) => { | ||
| const parts = [ | ||
| `${rpad(label, maxLength)} ${chalk.green(`${passed} passed`)}` | ||
| ]; | ||
|
|
||
| if (failed > 0) parts.push(chalk.red(`${failed} failed`)); | ||
| if (errorCount > 0) parts.push(chalk.red(`${errorCount} error`)); | ||
| if (skippedCount > 0) parts.push(chalk.magenta(`${skippedCount} skipped`)); | ||
|
|
||
| parts.push(`${total} total`); | ||
| const formatRequestsCellFromSummary = (summary) => { | ||
| const total = summary.totalRequests || 0; | ||
| const passed = summary.passedRequests || 0; | ||
| const failedOrErrored = (summary.failedRequests || 0) + (summary.errorRequests || 0); | ||
| const totalSkipped = summary.skippedRequests || 0; | ||
| const skippedByBail = summary.skippedByBail || 0; | ||
| const skippedByUser = Math.max(totalSkipped - skippedByBail, 0); | ||
|
|
||
| const parts = []; | ||
| if (passed > 0) parts.push(chalk.green(`${passed} Passed`)); | ||
| if (failedOrErrored > 0) parts.push(chalk.red(`${failedOrErrored} Failed`)); | ||
| if (skippedByUser > 0) parts.push(chalk.magenta(`${skippedByUser} Skipped`)); | ||
| if (skippedByBail > 0) parts.push(chalk.hex(constants.COLORS.ORANGE)(`${skippedByBail} Skipped (Bail)`)); | ||
|
|
||
| return parts.length ? `${total} (${parts.join(', ')})` : `${total}`; | ||
| }; | ||
|
|
||
| return parts.join(', '); | ||
| const printGenericTable = (headers, rows, title) => { | ||
| const colAligns = headers.map((_, idx) => (idx === 0 ? 'left' : 'center')); | ||
| const table = new Table({ head: headers, style: { head: [], border: [] }, colAligns }); | ||
| rows.forEach((row) => table.push(row)); | ||
| console.log('\n' + chalk.bold(title)); | ||
| console.log(table.toString()); | ||
| }; | ||
|
|
||
| const printRunSummary = (results) => { | ||
| const { | ||
| totalRequests, | ||
| passedRequests, | ||
| failedRequests, | ||
| skippedRequests, | ||
| errorRequests, | ||
| totalAssertions, | ||
| passedAssertions, | ||
| failedAssertions, | ||
| totalTests, | ||
| passedTests, | ||
| failedTests, | ||
| totalPreRequestTests, | ||
| passedPreRequestTests, | ||
| failedPreRequestTests, | ||
| totalPostResponseTests, | ||
| passedPostResponseTests, | ||
| failedPostResponseTests | ||
| } = getRunnerSummary(results); | ||
|
|
||
| const maxLength = 12; | ||
|
|
||
| const requestSummary = formatTestSummary('Requests:', maxLength, passedRequests, failedRequests, totalRequests, errorRequests, skippedRequests); | ||
| const testSummary = formatTestSummary('Tests:', maxLength, passedTests, failedTests, totalTests); | ||
| const assertSummary = formatTestSummary('Assertions:', maxLength, passedAssertions, failedAssertions, totalAssertions); | ||
|
|
||
| let preRequestTestSummary = ''; | ||
| if (totalPreRequestTests > 0) { | ||
| preRequestTestSummary = formatTestSummary('Pre-Request Tests:', maxLength, passedPreRequestTests, failedPreRequestTests, totalPreRequestTests); | ||
| } | ||
| const summary = getRunnerSummary(results); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the summary should already get bail information extracted using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. addressed now its coming from getRunnerSummary(results) |
||
|
|
||
| const duration = Math.round( | ||
| results.reduce((acc, res) => acc + (res.runDuration || 0), 0) * 1000 | ||
| ); | ||
|
|
||
| const hasFailures | ||
| = summary.failedRequests > 0 | ||
| || summary.failedAssertions > 0 | ||
| || summary.failedTests > 0 | ||
| || (summary.errorRequests || 0) > 0; | ||
|
|
||
| const status = hasFailures | ||
| ? chalk.red.bold('✗ FAIL') | ||
| : chalk.green.bold('✓ PASS'); | ||
|
|
||
| const requests = formatRequestsCellFromSummary(summary); | ||
| const tests = `${summary.passedTests}/${summary.totalTests}`; | ||
| const assertions = `${summary.passedAssertions}/${summary.totalAssertions}`; | ||
|
|
||
| const headers = [chalk.bold('Metric'), chalk.bold('Result')]; | ||
| const rows = [ | ||
| ['Status', status], | ||
| ['Requests', requests], | ||
| ['Tests', tests], | ||
| ['Assertions', assertions], | ||
| ['Duration (ms)', duration] | ||
| ]; | ||
|
|
||
| let postResponseTestSummary = ''; | ||
| if (totalPostResponseTests > 0) { | ||
| postResponseTestSummary = formatTestSummary('Post-Response Tests:', maxLength, passedPostResponseTests, failedPostResponseTests, totalPostResponseTests); | ||
| } | ||
| printGenericTable(headers, rows, '📊 Execution Summary'); | ||
|
|
||
| console.log('\n' + chalk.bold(requestSummary)); | ||
| if (preRequestTestSummary) { | ||
| console.log(chalk.bold(preRequestTestSummary)); | ||
| } | ||
| if (postResponseTestSummary) { | ||
| console.log(chalk.bold(postResponseTestSummary)); | ||
| } | ||
| console.log(chalk.bold(testSummary)); | ||
| console.log(chalk.bold(assertSummary)); | ||
|
|
||
| return { | ||
| totalRequests, | ||
| passedRequests, | ||
| failedRequests, | ||
| skippedRequests, | ||
| errorRequests, | ||
| totalAssertions, | ||
| passedAssertions, | ||
| failedAssertions, | ||
| totalTests, | ||
| passedTests, | ||
| failedTests, | ||
| totalPreRequestTests, | ||
| passedPreRequestTests, | ||
| failedPreRequestTests, | ||
| totalPostResponseTests, | ||
| passedPostResponseTests, | ||
| failedPostResponseTests | ||
| }; | ||
| return summary; | ||
| }; | ||
|
|
||
| const getJsSandboxRuntime = (sandbox) => { | ||
|
|
@@ -679,6 +657,7 @@ const handler = async function (argv) { | |
|
|
||
| let currentRequestIndex = 0; | ||
| let nJumps = 0; // count the number of jumps to avoid infinite loops | ||
| let bailInfo = null; // populated only if --bail triggers | ||
| while (currentRequestIndex < requestItems.length) { | ||
| const requestItem = cloneDeep(requestItems[currentRequestIndex]); | ||
| const { name, pathname } = requestItem; | ||
|
|
@@ -712,7 +691,7 @@ const handler = async function (argv) { | |
| results.push({ | ||
| ...result, | ||
| runDuration: process.hrtime(start)[0] + process.hrtime(start)[1] / 1e9, | ||
| suitename: pathname.replace('.bru', ''), | ||
| suitename: stripExtension(pathname), | ||
| name, | ||
| path: result.test?.filename || path.relative(collectionPath, pathname) | ||
| }); | ||
|
|
@@ -732,6 +711,64 @@ const handler = async function (argv) { | |
| const preRequestTestFailure = result?.preRequestTestResults?.find((iter) => iter.status === 'fail'); | ||
| const postResponseTestFailure = result?.postResponseTestResults?.find((iter) => iter.status === 'fail'); | ||
| if (requestFailure || testFailure || assertionFailure || preRequestTestFailure || postResponseTestFailure) { | ||
| // Pick the most specific reason for the user-facing message | ||
| let bailReason; | ||
| if (requestFailure) bailReason = 'request failure'; | ||
| else if (assertionFailure) bailReason = 'assertion failure'; | ||
| else if (preRequestTestFailure) bailReason = 'pre-request test failure'; | ||
| else if (postResponseTestFailure) bailReason = 'post-response test failure'; | ||
| else bailReason = 'test failure'; | ||
|
|
||
| const remainingItems = requestItems.slice(currentRequestIndex + 1); | ||
|
|
||
| // Synthesize "Skipped (Bail)" placeholder results for the requests that never | ||
| // ran due to bail. These let getRunnerSummary count them as skipped, and the | ||
| // summary table can distinguish them from user-initiated skips via skipReason. | ||
| for (const ri of remainingItems) { | ||
| const relativePath = path.relative(collectionPath, ri.pathname); | ||
| results.push({ | ||
| test: { | ||
| filename: relativePath | ||
| }, | ||
| request: { | ||
| method: null, | ||
| url: null, | ||
| headers: null, | ||
| data: null | ||
| }, | ||
| response: { | ||
| status: 'skipped', | ||
| statusText: null, | ||
| data: null, | ||
| responseTime: 0 | ||
| }, | ||
| status: 'skipped', | ||
| skipped: true, | ||
| skipReason: 'bail', | ||
| testResults: [], | ||
| assertionResults: [], | ||
| preRequestTestResults: [], | ||
| postResponseTestResults: [], | ||
| runDuration: 0, | ||
| suitename: stripExtension(ri.pathname), | ||
| name: ri.name, | ||
| path: relativePath | ||
| }); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| bailInfo = { | ||
| bailed: true, | ||
| bailReason, | ||
| bailedAt: name, | ||
| skippedByBail: remainingItems.length | ||
| }; | ||
|
|
||
| console.log( | ||
| '\n' + chalk.hex(constants.COLORS.ORANGE)( | ||
| `Bail: Stopping run, ${bailReason} in "${name}". Remaining ${remainingItems.length} request(s) skipped.` | ||
| ) | ||
| ); | ||
|
|
||
| break; | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -88,6 +88,8 @@ export type T_RunnerRequestExecutionResult = { | |
| request: T_EmptyRequest | T_Request; | ||
| response: T_EmptyResponse | T_Response | T_SkippedResponse; | ||
| status: null | undefined | string; | ||
| skipped?: boolean; | ||
| skipReason?: string; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we are missing
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added the missing skipped in the type |
||
| error: null | undefined | string; | ||
| assertionResults?: T_AssertionResult[]; | ||
| testResults?: T_TestResult[]; | ||
|
|
@@ -110,6 +112,7 @@ export type T_RunSummary = { | |
| failedRequests: number; | ||
| errorRequests: number; | ||
| skippedRequests: number; | ||
| skippedByBail: number; | ||
| totalAssertions: number; | ||
| passedAssertions: number; | ||
| failedAssertions: number; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.