Skip to content

Commit

Permalink
Merge pull request #361 from mrmlnc/use_async_method_instead_of_stream
Browse files Browse the repository at this point in the history
perf: use fs.walk instead of fs.walkStream for async provider
  • Loading branch information
mrmlnc authored Jun 4, 2022
2 parents 3c243c2 + 7268b50 commit 2ff5a2f
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 29 deletions.
20 changes: 5 additions & 15 deletions src/providers/async.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as assert from 'assert';
import { PassThrough } from 'stream';

import * as sinon from 'sinon';

Expand All @@ -8,10 +7,11 @@ import ReaderStream from '../readers/stream';
import Settings, { Options } from '../settings';
import * as tests from '../tests';
import { Entry, EntryItem, ErrnoException } from '../types';
import ReaderAsync from '../readers/async';
import ProviderAsync from './async';

class TestProvider extends ProviderAsync {
protected _reader: ReaderStream = sinon.createStubInstance(ReaderStream) as unknown as ReaderStream;
protected _reader: ReaderAsync = sinon.createStubInstance(ReaderAsync) as unknown as ReaderAsync;

constructor(options?: Options) {
super(new Settings(options));
Expand All @@ -27,13 +27,8 @@ function getProvider(options?: Options): TestProvider {
}

function getEntries(provider: TestProvider, task: Task, entry: Entry): Promise<EntryItem[]> {
const reader = new PassThrough({ objectMode: true });

provider.reader.dynamic.returns(reader);
provider.reader.static.returns(reader);

reader.push(entry);
reader.push(null);
provider.reader.dynamic.resolves([entry]);
provider.reader.static.resolves([entry]);

return provider.read(task);
}
Expand Down Expand Up @@ -77,13 +72,8 @@ describe('Providers → ProviderAsync', () => {
it('should throw error', async () => {
const provider = getProvider();
const task = tests.task.builder().base('.').positive('*').build();
const stream = new PassThrough({
read(): void {
stream.emit('error', tests.errno.getEnoent());
}
});

provider.reader.dynamic.returns(stream);
provider.reader.dynamic.rejects(tests.errno.getEnoent());

try {
await provider.read(task);
Expand Down
20 changes: 6 additions & 14 deletions src/providers/async.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,21 @@
import { Readable } from 'stream';

import { Task } from '../managers/tasks';
import ReaderStream from '../readers/stream';
import { Entry, EntryItem, ReaderOptions } from '../types';
import ReaderAsync from '../readers/async';
import Provider from './provider';

export default class ProviderAsync extends Provider<Promise<EntryItem[]>> {
protected _reader: ReaderStream = new ReaderStream(this._settings);
protected _reader: ReaderAsync = new ReaderAsync(this._settings);

public read(task: Task): Promise<EntryItem[]> {
public async read(task: Task): Promise<EntryItem[]> {
const root = this._getRootDirectory(task);
const options = this._getReaderOptions(task);

const entries: EntryItem[] = [];

return new Promise((resolve, reject) => {
const stream = this.api(root, task, options);
const entries = await this.api(root, task, options);

stream.once('error', reject);
stream.on('data', (entry: Entry) => entries.push(options.transform(entry)));
stream.once('end', () => resolve(entries));
});
return entries.map((entry) => options.transform(entry));
}

public api(root: string, task: Task, options: ReaderOptions): Readable {
public api(root: string, task: Task, options: ReaderOptions): Promise<Entry[]> {
if (task.dynamic) {
return this._reader.dynamic(root, options);
}
Expand Down
79 changes: 79 additions & 0 deletions src/readers/async.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as assert from 'assert';

import { PassThrough } from 'stream';
import * as sinon from 'sinon';
import * as fsWalk from '@nodelib/fs.walk';
import Settings, { Options } from '../settings';
import { ReaderOptions } from '../types';
import * as tests from '../tests';
import ReaderAsync from './async';
import ReaderStream from './stream';

type WalkSignature = typeof fsWalk.walk;

class TestReader extends ReaderAsync {
protected _walkAsync: WalkSignature = sinon.stub() as unknown as WalkSignature;
protected _readerStream: ReaderStream = sinon.createStubInstance(ReaderStream) as unknown as ReaderStream;

constructor(options?: Options) {
super(new Settings(options));
}

public get walkAsync(): sinon.SinonStub {
return this._walkAsync as unknown as sinon.SinonStub;
}

public get readerStream(): sinon.SinonStubbedInstance<ReaderStream> {
return this._readerStream as unknown as sinon.SinonStubbedInstance<ReaderStream>;
}
}

function getReader(options?: Options): TestReader {
return new TestReader(options);
}

function getReaderOptions(options: Partial<ReaderOptions> = {}): ReaderOptions {
return { ...options } as unknown as ReaderOptions;
}

describe('Readers → ReaderAsync', () => {
describe('Constructor', () => {
it('should create instance of class', () => {
const reader = getReader();

assert.ok(reader instanceof TestReader);
});
});

describe('.dynamic', () => {
it('should call fs.walk method', async () => {
const reader = getReader();
const readerOptions = getReaderOptions();

reader.walkAsync.yields(null, []);

await reader.dynamic('root', readerOptions);

assert.ok(reader.walkAsync.called);
});
});

describe('.static', () => {
it('should call stream reader method', async () => {
const entry = tests.entry.builder().path('root/file.txt').build();

const reader = getReader();
const readerOptions = getReaderOptions();
const readerStream = new PassThrough({ objectMode: true });

readerStream.push(entry);
readerStream.push(null);

reader.readerStream.static.returns(readerStream);

await reader.static(['a.txt'], readerOptions);

assert.ok(reader.readerStream.static.called);
});
});
});
34 changes: 34 additions & 0 deletions src/readers/async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as fsWalk from '@nodelib/fs.walk';
import { Entry, ReaderOptions, Pattern } from '../types';
import Reader from './reader';
import ReaderStream from './stream';

export default class ReaderAsync extends Reader<Promise<Entry[]>> {
protected _walkAsync: typeof fsWalk.walk = fsWalk.walk;
protected _readerStream: ReaderStream = new ReaderStream(this._settings);

public dynamic(root: string, options: ReaderOptions): Promise<Entry[]> {
return new Promise((resolve, reject) => {
this._walkAsync(root, options, (error, entries) => {
if (error === null) {
resolve(entries);
} else {
reject(error);
}
});
});
}

public async static(patterns: Pattern[], options: ReaderOptions): Promise<Entry[]> {
const entries: Entry[] = [];

const stream = this._readerStream.static(patterns, options);

// After #235, replace it with an asynchronous iterator.
return new Promise((resolve, reject) => {
stream.once('error', reject);
stream.on('data', (entry: Entry) => entries.push(entry));
stream.once('end', () => resolve(entries));
});
}
}

0 comments on commit 2ff5a2f

Please sign in to comment.