Skip to content

Commit

Permalink
Fix ERR_STREAM_WRITE_AFTER_END when processing more than one log file
Browse files Browse the repository at this point in the history
  • Loading branch information
lcampos committed Jul 22, 2020
1 parent c1fa737 commit 039f1f4
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 49 deletions.
23 changes: 7 additions & 16 deletions packages/apex-node/src/logs/logService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { Connection } from '@salesforce/core';
import { ApexLogGetOptions } from '../types';
import { QueryResult } from '../types/common';
import { nls } from '../i18n';
import * as fs from 'fs';
import * as path from 'path';
import { createFiles } from '../utils';

const MAX_NUM_LOGS = 25;

Expand All @@ -29,30 +29,21 @@ export class LogService {
logIdList.push(options.logId);
}

const saveLogsMap = new Map();

const connectionRequests = logIdList.map(async id => {
const url = `${this.connection.tooling._baseUrl()}/sobjects/ApexLog/${id}/Body`;
const logRecord = await this.toolingRequest(url);
if (options.outputDir) {
this.writeLog(options.outputDir, logRecord, id);
saveLogsMap.set(path.join(options.outputDir, `${id}.log`), logRecord);
}
return logRecord;
});

const result = await Promise.all(connectionRequests);
return result;
}
createFiles(saveLogsMap);

public async writeLog(
outputDir: string,
logRecord: string,
id: string
): Promise<void> {
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
const filePath = path.join(`${outputDir}`, `${id}.log`);
const stream = fs.createWriteStream(filePath);
stream.write(logRecord);
stream.end();
return result;
}

public async getLogIds(numberOfLogs: number): Promise<string[]> {
Expand Down
37 changes: 37 additions & 0 deletions packages/apex-node/src/utils/fileSystemHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import * as fs from 'fs';
import * as path from 'path';

export function ensureDirectoryExists(filePath: string): void {
if (fs.existsSync(filePath)) {
return;
}
ensureDirectoryExists(path.dirname(filePath));
fs.mkdirSync(filePath);
}

export function ensureFileExists(filePath: string): void {
ensureDirectoryExists(path.dirname(filePath));
fs.closeSync(fs.openSync(filePath, 'w'));
}

/**
* Method to save multiple files on disk.
*
* @param fileMap key = filePath, value = file contents
*/
export function createFiles(fileMap: Map<string, string>): void {
for (const filePath of fileMap.keys()) {
ensureFileExists(filePath);

const writeStream = fs.createWriteStream(filePath);
writeStream.write(fileMap.get(filePath));
writeStream.end();
}
}
8 changes: 8 additions & 0 deletions packages/apex-node/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

export { createFiles } from './fileSystemHandler';
57 changes: 24 additions & 33 deletions packages/apex-node/test/logs/logService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import * as fs from 'fs';
import { createSandbox, SinonSandbox, SinonStub } from 'sinon';
import { LogService } from '../../src/logs/logService';
import * as path from 'path';
import * as stream from 'stream';

const $$ = testSetup();

describe('Apex Log Service Tests', () => {
const testData = new MockTestOrgData();
let mockConnection: Connection;
let sandboxStub: SinonSandbox;
let mkdirStub: SinonStub;
let toolingRequestStub: SinonStub;

beforeEach(async () => {
Expand All @@ -32,7 +32,6 @@ describe('Apex Log Service Tests', () => {
username: testData.username
})
});
mkdirStub = sandboxStub.stub(fs, 'mkdirSync');
toolingRequestStub = sandboxStub.stub(
LogService.prototype,
'toolingRequest'
Expand Down Expand Up @@ -132,7 +131,15 @@ describe('Apex Log Service Tests', () => {
const filePath = path.join('file', 'path', 'logs');
const logIds = ['07WgsWfad', '9SiomgS'];
sandboxStub.stub(LogService.prototype, 'getLogIds').resolves(logIds);

const createStreamStub = sandboxStub.stub(fs, 'createWriteStream');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
createStreamStub.onCall(0).returns(new stream.PassThrough() as any);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
createStreamStub.onCall(1).returns(new stream.PassThrough() as any);
sandboxStub.stub(fs, 'closeSync');
sandboxStub.stub(fs, 'openSync');

const logs = ['48jnskd', '57fskjf'];
toolingRequestStub.onFirstCall().resolves(logs[0]);
toolingRequestStub.onSecondCall().resolves(logs[1]);
Expand All @@ -144,42 +151,26 @@ describe('Apex Log Service Tests', () => {
expect(createStreamStub.callCount).to.eql(2);
});

it('should create directory if it does not exist', async () => {
it('should successfully create a .log file', async () => {
const apexLogGet = new LogService(mockConnection);
const filePath = path.join('Users', 'smit.shah', 'Desktop', 'mod');
const logIds = ['07WgsWfad', '9SiomgS'];
const logs = ['48jnskd', '57fskjf'];
sandboxStub.stub(LogService.prototype, 'getLogIds').resolves(logIds);
const existsStub = sandboxStub.stub(fs, 'existsSync');
existsStub.onFirstCall().returns(false);
existsStub.onSecondCall().returns(true);
const filePath = path.join('path', 'to', 'logs');
const logIds = ['07WgsWfad'];
const logs = ['log content'];
const logsPath = path.join(filePath, `${logIds[0]}.log`);
sandboxStub.stub(fs, 'existsSync').returns(true);
const createStreamStub = sandboxStub.stub(fs, 'createWriteStream');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
createStreamStub.onCall(0).returns(new stream.PassThrough() as any);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
createStreamStub.onCall(1).returns(new stream.PassThrough() as any);
sandboxStub.stub(fs, 'closeSync');
sandboxStub.stub(fs, 'openSync');
toolingRequestStub.onFirstCall().resolves(logs[0]);
toolingRequestStub.onSecondCall().resolves(logs[1]);
const response = await apexLogGet.getLogs({
numberOfLogs: 2,
await apexLogGet.getLogs({
logId: '07WgsWfad',
outputDir: filePath
});
expect(response.length).to.eql(2);
expect(createStreamStub.callCount).to.eql(2);
expect(existsStub.callCount).to.eql(2);
expect(mkdirStub.callCount).to.eql(1);
});

it('should successfully create a .log file', async () => {
const apexLogGet = new LogService(mockConnection);
const filePath = path.join('Users', 'smit.shah', 'Desktop', 'mod');
const logIds = ['07WgsWfad', '9SiomgS'];
const logs = ['48jnskd', '57fskjf'];
const logsPath = path.join(filePath, `${logIds[0]}.log`)
const createStreamStub = sandboxStub.stub(fs, 'createWriteStream').returns({
//@ts-ignore
write: () => {},
end: () => {}
});
toolingRequestStub.onFirstCall().resolves(logs[0]);
toolingRequestStub.onSecondCall().resolves(logs[1]);
await apexLogGet.writeLog(filePath,logs[0],logIds[0]);
expect(createStreamStub.calledWith(logsPath)).to.be.true;
});
});
});
70 changes: 70 additions & 0 deletions packages/apex-node/test/utils/fileSystemHandler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import * as fsUtil from '../../src/utils/fileSystemHandler';
import { createSandbox, SinonStub } from 'sinon';
import { expect } from 'chai';
import { join } from 'path';
import * as fs from 'fs';

const sb = createSandbox();

describe('File System Utils', () => {
describe('ensureDirectoryExists', () => {
let mkdirStub: SinonStub;
let existsStub: SinonStub;

beforeEach(() => {
mkdirStub = sb.stub(fs, 'mkdirSync');
existsStub = sb.stub(fs, 'existsSync');
});

afterEach(() => {
sb.restore();
});

it('should return immediately if file or directory already exists', () => {
const path = join('path', 'to', 'dir');
existsStub.withArgs(path).returns(true);

fsUtil.ensureDirectoryExists(path);

expect(mkdirStub.notCalled).to.be.true;
});

it('should create nested directories as needed', () => {
const path = join('path', 'to');
const path2 = join(path, 'dir');
const path3 = join(path2, 'dir2');
existsStub.returns(false);
existsStub.withArgs(path).returns(true);

fsUtil.ensureDirectoryExists(path3);

expect(mkdirStub.firstCall.args[0]).to.equal(path2);
expect(mkdirStub.secondCall.args[0]).to.equal(path3);
});
});

describe('ensureFileExists', () => {
afterEach(() => {
sb.restore();
});

it('should ensure file exists', () => {
const path = join('path', 'to', 'a', 'file.x');
const closeStub = sb.stub(fs, 'closeSync');
const openStub = sb.stub(fs, 'openSync');
openStub.returns(123);
const existsSyncStub = sb.stub(fs, 'existsSync').returns(true);

fsUtil.ensureFileExists(path);

expect(existsSyncStub.calledBefore(openStub)).to.be.true;
expect(closeStub.firstCall.args[0]).to.equal(123);
});
});
});

0 comments on commit 039f1f4

Please sign in to comment.