From e69bebc81e47ab3465f537b2895b0920544f672d Mon Sep 17 00:00:00 2001 From: Maksim Gruzdev Date: Mon, 27 Jan 2025 00:12:07 +0300 Subject: [PATCH 1/2] #113 print mr.test output as it goes --- lib/ShellCommand.js | 58 +++++++++++++++++++++++++------------------ mr.js | 2 +- tests/ShellCommand.js | 36 ++++++++++++++++++++++++--- 3 files changed, 67 insertions(+), 29 deletions(-) diff --git a/lib/ShellCommand.js b/lib/ShellCommand.js index 42d8a7f..df521df 100644 --- a/lib/ShellCommand.js +++ b/lib/ShellCommand.js @@ -1,5 +1,4 @@ -const assert = require('node:assert/strict') -const { exec } = require('child_process') +const { spawn } = require('child_process') // @todo #0:30m decompose // runSilent go to ShellCommand @@ -7,46 +6,57 @@ const { exec } = require('child_process') module.exports = class ShellCommand { static withText (cmd) { - return new ShellCommand ({ cmd, exec }) + return new ShellCommand ({ cmd, spawn }) } constructor(o) { this.cwd = o.cwd this.cmd = o.cmd - this.exec = o.exec + this.spawn = o.spawn } async run () { let cwdLabel = this.cwd ? ` (${this.cwd})` : '' this.log(`${cwdLabel} > ${await this.print ()}`) - let result = await this.runSilent() - if (result) { - this.log(result.trim ()) - } - return result + return this.runSilent({log_flow: true}) } - async runSilent () { + async runSilent (options = {}) { + const {log_flow} = options if (global.FUZZ) { // @todo #120:1h move to injected ShellCommandFuzz return global.FUZZ_SHELL_REPLY } - return new Promise((resolve, reject) => { - this.exec(this.cmd, {cwd: this.cwd}, (error, stdout, stderr) => { - if (error) { - let label = error.message - if (label && /^Command failed: /i.test (label) && stdout) { - label = stdout - } - reject (new Error (label)) - return - } - if (stderr) { - resolve(stderr) - return + return new Promise((ok, fail) => { + const [cmd, ...args] = this.cmd.split(' ') + const env = { ...process.env, + TERM: 'xterm-256color' + } + const o = {env} + const subprocess = this.spawn(cmd, args, o) + + let stdout = '', stderr = '' + subprocess.stdout.on('data', (data) => { + if (log_flow) process.stdout.write(data) + stdout = stdout + data + }) + + subprocess.stderr.on('data', (data) => { + if (log_flow) process.stdout.write(data) + stderr = stderr + data + }) + + subprocess.on('exit', (code) => { + if (code === 0) { + ok(stdout.toString().trim()) + } else { + fail(new Error(stderr.toString().trim())) } - resolve (stdout.trim ()) + }) + + subprocess.on('error', (error) => { + fail(new Error(error.toString().trim())) }) }) } diff --git a/mr.js b/mr.js index 8d603b2..1a794d7 100644 --- a/mr.js +++ b/mr.js @@ -28,7 +28,7 @@ module.exports = { try { await main (process.argv.slice (2)) } catch (x) { - console.log (new Error (x).message) + // console.log (new Error (x).message) return '' + x } })() diff --git a/tests/ShellCommand.js b/tests/ShellCommand.js index 096c570..bc7d9bd 100644 --- a/tests/ShellCommand.js +++ b/tests/ShellCommand.js @@ -4,6 +4,34 @@ const ShellCommand = require ('../lib/ShellCommand') describe('random input', () => { + + const makeSpawnMock = (o = {}) => { + + const {stdout, stderr, close} = o + + return (command, args, options) => ({ + stdout: { + on: (event, callback) => { + if (event === 'data') { + return callback(`${stdout}\n`) + } + }, + }, + stderr: { + on: (event, callback) => { + if (event === 'data') { + return callback(`${stderr}\n`) + } + }, + }, + on: (event, callback) => { + if (event === 'exit') { + return callback(o.exit) + } + }, + }) + } + it ('exit 1', async (t) => { assert.rejects(async () => { await ShellCommand.withText ('exit 1').run () @@ -15,13 +43,13 @@ describe('random input', () => { }) it ('exec ok', async (t) => { - const exec = (cmd, o, callback) => callback ('', '', '') - assert.strictEqual(await (new ShellCommand ({cmd: 'git fetch', exec}).runSilent ()), '') + const spawn = makeSpawnMock ({stdout: '', stderr: '', exit: 0}) + assert.strictEqual(await (new ShellCommand ({cmd: 'git fetch', spawn}).runSilent ()), '') }) it ('exec fail', async (t) => { - const exec = (cmd, o, callback) => callback (new Error ('Command failed: '), 'not a repo', '') - assert.rejects(new ShellCommand ({cmd: 'git fetch', exec}).runSilent (), {message: 'not a repo'}) + const spawn = makeSpawnMock ({stdout: '', stderr: 'not a repo', exit: -127}) + assert.rejects(new ShellCommand ({cmd: 'git fetch', spawn}).runSilent (), {message: 'not a repo'}) }) it ('git status fuzz', async (t) => { From 7a053d49318132205f1e380dfc06b52ff6b6e696 Mon Sep 17 00:00:00 2001 From: Maksim Gruzdev Date: Mon, 27 Jan 2025 23:30:30 +0300 Subject: [PATCH 2/2] #113 FIX old git '|| true' fallback run --- lib/ShellCommand.js | 8 +++++--- mr.js | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/ShellCommand.js b/lib/ShellCommand.js index df521df..55a9832 100644 --- a/lib/ShellCommand.js +++ b/lib/ShellCommand.js @@ -27,9 +27,11 @@ module.exports = class ShellCommand { // @todo #120:1h move to injected ShellCommandFuzz return global.FUZZ_SHELL_REPLY } - return new Promise((ok, fail) => { - const [cmd, ...args] = this.cmd.split(' ') + const [the_cmd, tail] = this.cmd.split(' || true') + const is_always_ok = tail === '' + const [cmd, ...args] = the_cmd.split(' ') + const env = { ...process.env, TERM: 'xterm-256color' } @@ -48,7 +50,7 @@ module.exports = class ShellCommand { }) subprocess.on('exit', (code) => { - if (code === 0) { + if (code === 0 || is_always_ok) { ok(stdout.toString().trim()) } else { fail(new Error(stderr.toString().trim())) diff --git a/mr.js b/mr.js index 1a794d7..8d603b2 100644 --- a/mr.js +++ b/mr.js @@ -28,7 +28,7 @@ module.exports = { try { await main (process.argv.slice (2)) } catch (x) { - // console.log (new Error (x).message) + console.log (new Error (x).message) return '' + x } })()