Skip to content
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

Fix ToolRunner stdline/errline events buffering #1055

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions node/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## 4.x

## 4.17.2

- Fix ToolRunner stdline/errline events buffering - [#1055](https://github.com/microsoft/azure-pipelines-task-lib/pull/1055)

## 4.17.1

- Fix debug logs inside user commands - [#1064](https://github.com/microsoft/azure-pipelines-task-lib/pull/1064)
Expand Down
2 changes: 1 addition & 1 deletion node/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion node/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "azure-pipelines-task-lib",
"version": "4.17.1",
"version": "4.17.2",
"description": "Azure Pipelines Task SDK",
"main": "./task.js",
"typings": "./task.d.ts",
Expand Down
12 changes: 12 additions & 0 deletions node/test/scripts/write-bufferedoutput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const os = require('os');

const stdout = process.stdout;
const stderr = process.stderr;

stdout.write('stdline 1' + os.EOL,
() => stdout.write('stdline 2',
() => stdout.write(os.EOL + 'stdline 3')));

stderr.write('errline 1' + os.EOL,
() => stderr.write('errline 2',
() => stderr.write(os.EOL + 'errline 3')));
30 changes: 30 additions & 0 deletions node/test/toolrunnerTestsWithExecAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,36 @@ describe('Toolrunner Tests With ExecAsync', function () {
});
}
})
it('Writes correct output line events', async function () {
const scriptPath = path.join(__dirname, 'scripts', 'write-bufferedoutput.js');
const node = tl.tool(tl.which('node', true));
node.arg(scriptPath);

const stdlines = [];
const errlines = [];

node.on('stdline', function (line) {
stdlines.push(line);
});

node.on('errline', function (line) {
errlines.push(line);
});

const code = await node.execAsync({
cwd: __dirname,
env: {},
silent: false,
failOnStdErr: false,
ignoreReturnCode: false,
outStream: testutil.getNullStream(),
errStream: testutil.getNullStream()
})

assert.deepStrictEqual(code, 0, 'return code of cmd should be 0');
assert.deepStrictEqual(stdlines, ['stdline 1', 'stdline 2', 'stdline 3'], 'should have emitted stdlines');
assert.deepStrictEqual(errlines, ['errline 1', 'errline 2', 'errline 3'], 'should have emitted errlines');
})
it('Execs with stdout', function (done) {
this.timeout(10000);

Expand Down
30 changes: 30 additions & 0 deletions node/test/toolrunnertests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,36 @@ describe('Toolrunner Tests', function () {
});
}
})
it('Writes correct output line events', async function () {
const scriptPath = path.join(__dirname, 'scripts', 'write-bufferedoutput.js');
const node = tl.tool(tl.which('node', true));
node.arg(scriptPath);

const stdlines = [];
const errlines = [];

node.on('stdline', function (line) {
stdlines.push(line);
});

node.on('errline', function (line) {
errlines.push(line);
});

const code = await node.exec({
cwd: __dirname,
env: {},
silent: false,
failOnStdErr: false,
ignoreReturnCode: false,
outStream: testutil.getNullStream(),
errStream: testutil.getNullStream()
})

assert.deepStrictEqual(code, 0, 'return code of cmd should be 0');
assert.deepStrictEqual(stdlines, ['stdline 1', 'stdline 2', 'stdline 3'], 'should have emitted stdlines');
assert.deepStrictEqual(errlines, ['errline 1', 'errline 2', 'errline 3'], 'should have emitted errlines');
})
it('Execs with stdout', function (done) {
this.timeout(10000);

Expand Down
92 changes: 47 additions & 45 deletions node/toolrunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import Q = require('q');
import os = require('os');
import events = require('events');
import child = require('child_process');
import stream = require('stream');
import im = require('./internal');
import fs = require('fs');

Expand Down Expand Up @@ -194,27 +193,27 @@ export class ToolRunner extends events.EventEmitter {
return cmd;
}

private _processLineBuffer(data: Buffer, strBuffer: string, onLine: (line: string) => void): void {
private _processLineBuffer(data: Buffer, buffer: string, onLine: (line: string) => void): string {
let newBuffer = buffer + data.toString();

try {
var s = strBuffer + data.toString();
var n = s.indexOf(os.EOL);
let eolIndex = newBuffer.indexOf(os.EOL);

while (n > -1) {
var line = s.substring(0, n);
while (eolIndex > -1) {
const line = newBuffer.substring(0, eolIndex);
onLine(line);

// the rest of the string ...
s = s.substring(n + os.EOL.length);
n = s.indexOf(os.EOL);
newBuffer = newBuffer.substring(eolIndex + os.EOL.length);
eolIndex = newBuffer.indexOf(os.EOL);
}

strBuffer = s;
}
catch (err) {
// streaming lines to console is best effort. Don't fail a build.
this._debug('error processing line');
}

return newBuffer;
}

/**
Expand Down Expand Up @@ -728,20 +727,20 @@ export class ToolRunner extends events.EventEmitter {
}
});

var stdbuffer: string = '';
let stdLineBuffer = '';
cp.stdout?.on('data', (data: Buffer) => {
this.emit('stdout', data);

if (!optionsNonNull.silent) {
optionsNonNull.outStream!.write(data);
}

this._processLineBuffer(data, stdbuffer, (line: string) => {
stdLineBuffer = this._processLineBuffer(data, stdLineBuffer, (line: string) => {
this.emit('stdline', line);
});
});

var errbuffer: string = '';
let errLineBuffer = '';
cp.stderr?.on('data', (data: Buffer) => {
this.emit('stderr', data);

Expand All @@ -751,7 +750,7 @@ export class ToolRunner extends events.EventEmitter {
s.write(data);
}

this._processLineBuffer(data, errbuffer, (line: string) => {
errLineBuffer = this._processLineBuffer(data, errLineBuffer, (line: string) => {
this.emit('errline', line);
});
});
Expand All @@ -769,12 +768,12 @@ export class ToolRunner extends events.EventEmitter {
this._debug('rc:' + code);
returnCode = code;

if (stdbuffer.length > 0) {
this.emit('stdline', stdbuffer);
if (stdLineBuffer.length > 0) {
this.emit('stdline', stdLineBuffer);
}

if (errbuffer.length > 0) {
this.emit('errline', errbuffer);
if (errLineBuffer.length > 0) {
this.emit('errline', errLineBuffer);
}

if (code != 0 && !optionsNonNull.ignoreReturnCode) {
Expand Down Expand Up @@ -926,20 +925,20 @@ export class ToolRunner extends events.EventEmitter {
}
});

var stdbuffer: string = '';
let stdLineBuffer = '';
cp.stdout?.on('data', (data: Buffer) => {
this.emit('stdout', data);

if (!optionsNonNull.silent) {
optionsNonNull.outStream!.write(data);
}

this._processLineBuffer(data, stdbuffer, (line: string) => {
stdLineBuffer = this._processLineBuffer(data, stdLineBuffer, (line: string) => {
this.emit('stdline', line);
});
});

var errbuffer: string = '';
let errLineBuffer = '';
cp.stderr?.on('data', (data: Buffer) => {
this.emit('stderr', data);

Expand All @@ -949,7 +948,7 @@ export class ToolRunner extends events.EventEmitter {
s.write(data);
}

this._processLineBuffer(data, errbuffer, (line: string) => {
errLineBuffer = this._processLineBuffer(data, errLineBuffer, (line: string) => {
this.emit('errline', line);
});
});
Expand All @@ -967,12 +966,12 @@ export class ToolRunner extends events.EventEmitter {
this._debug('rc:' + code);
returnCode = code;

if (stdbuffer.length > 0) {
this.emit('stdline', stdbuffer);
if (stdLineBuffer.length > 0) {
this.emit('stdline', stdLineBuffer);
}

if (errbuffer.length > 0) {
this.emit('errline', errbuffer);
if (errLineBuffer.length > 0) {
this.emit('errline', errLineBuffer);
}

if (code != 0 && !optionsNonNull.ignoreReturnCode) {
Expand Down Expand Up @@ -1100,16 +1099,19 @@ export class ToolRunner extends events.EventEmitter {
this._debug(message);
});

var stdbuffer: string = '';
var errbuffer: string = '';
const emitDoneEvent = function (resolve, reject) {
let stdLineBuffer = '';
let errLineBuffer = '';
const emitDoneEvent = (
resolve: (code: number) => void,
reject: (error: Error) => void
) => {
state.on('done', (error: Error, exitCode: number) => {
if (stdbuffer.length > 0) {
this.emit('stdline', stdbuffer);
if (stdLineBuffer.length > 0) {
this.emit('stdline', stdLineBuffer);
}

if (errbuffer.length > 0) {
this.emit('errline', errbuffer);
if (errLineBuffer.length > 0) {
this.emit('errline', errLineBuffer);
}

if (cp) {
Expand All @@ -1126,7 +1128,7 @@ export class ToolRunner extends events.EventEmitter {
}

// Edge case when the node itself cant's spawn and emit event
let cp;
let cp: child.ChildProcess;
try {
cp = child.spawn(this._getSpawnFileName(options), this._getSpawnArgs(optionsNonNull), this._getSpawnOptions(options));
} catch (error) {
Expand Down Expand Up @@ -1156,7 +1158,7 @@ export class ToolRunner extends events.EventEmitter {
optionsNonNull.outStream!.write(data);
}

this._processLineBuffer(data, stdbuffer, (line: string) => {
stdLineBuffer = this._processLineBuffer(data, stdLineBuffer, (line: string) => {
this.emit('stdline', line);
});
});
Expand All @@ -1170,7 +1172,7 @@ export class ToolRunner extends events.EventEmitter {
s.write(data);
}

this._processLineBuffer(data, errbuffer, (line: string) => {
errLineBuffer = this._processLineBuffer(data, errLineBuffer, (line: string) => {
this.emit('errline', line);
});
});
Expand Down Expand Up @@ -1234,15 +1236,15 @@ export class ToolRunner extends events.EventEmitter {
this._debug(message);
});

var stdbuffer: string = '';
var errbuffer: string = '';
let stdLineBuffer = '';
let errLineBuffer = '';
state.on('done', (error: Error, exitCode: number) => {
if (stdbuffer.length > 0) {
this.emit('stdline', stdbuffer);
if (stdLineBuffer.length > 0) {
this.emit('stdline', stdLineBuffer);
}

if (errbuffer.length > 0) {
this.emit('errline', errbuffer);
if (errLineBuffer.length > 0) {
this.emit('errline', errLineBuffer);
}

if (cp) {
Expand All @@ -1259,7 +1261,7 @@ export class ToolRunner extends events.EventEmitter {


// Edge case when the node itself cant's spawn and emit event
let cp;
let cp: child.ChildProcess;
try {
cp = child.spawn(this._getSpawnFileName(options), this._getSpawnArgs(optionsNonNull), this._getSpawnOptions(options));
} catch (error) {
Expand Down Expand Up @@ -1288,7 +1290,7 @@ export class ToolRunner extends events.EventEmitter {
optionsNonNull.outStream!.write(data);
}

this._processLineBuffer(data, stdbuffer, (line: string) => {
stdLineBuffer = this._processLineBuffer(data, stdLineBuffer, (line: string) => {
this.emit('stdline', line);
});
});
Expand All @@ -1303,7 +1305,7 @@ export class ToolRunner extends events.EventEmitter {
s.write(data);
}

this._processLineBuffer(data, errbuffer, (line: string) => {
errLineBuffer = this._processLineBuffer(data, errLineBuffer, (line: string) => {
this.emit('errline', line);
});
});
Expand Down