Skip to content

Commit b9fefcb

Browse files
committed
[kbn/optimizer] throw errors into stream on invalid completion
1 parent 918c0de commit b9fefcb

5 files changed

Lines changed: 167 additions & 4 deletions

File tree

packages/kbn-optimizer/src/common/rxjs_helpers.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ type Operator<T1, T2> = (source: Rx.Observable<T1>) => Rx.Observable<T2>;
2424
type MapFn<T1, T2> = (item: T1, index: number) => T2;
2525

2626
/**
27-
* Wrap an operator chain in a closure so that is can have some local
28-
* state. The `fn` is called each time the final observable is
29-
* subscribed so the pipeline/closure is setup for each subscription.
27+
* Wrap an operator chain in a closure so that it can have some local
28+
* state. The `fn` is called each time the returned observable is
29+
* subscribed; the closure is recreated for each subscription.
3030
*/
3131
export const pipeClosure = <T1, T2>(fn: Operator<T1, T2>): Operator<T1, T2> => {
3232
return (source: Rx.Observable<T1>) => {
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import * as Rx from 'rxjs';
21+
import { REPO_ROOT } from '@kbn/dev-utils';
22+
23+
import { Update } from '../common';
24+
25+
import { OptimizerState } from './optimizer_reducer';
26+
import { OptimizerConfig } from './optimizer_config';
27+
import { handleOptimizerCompletion } from './handle_optimizer_completion';
28+
import { toArray } from 'rxjs/operators';
29+
30+
const createUpdate$ = (phase: OptimizerState['phase']) =>
31+
Rx.of<Update<any, OptimizerState>>({
32+
state: {
33+
phase,
34+
compilerStates: [],
35+
durSec: 0,
36+
offlineBundles: [],
37+
onlineBundles: [],
38+
startTime: Date.now(),
39+
},
40+
});
41+
42+
const config = (watch?: boolean) =>
43+
OptimizerConfig.create({
44+
repoRoot: REPO_ROOT,
45+
watch,
46+
});
47+
const collect = <T>(stream: Rx.Observable<T>): Promise<T[]> => stream.pipe(toArray()).toPromise();
48+
49+
it('errors if the optimizer completes when in watch mode', async () => {
50+
const update$ = createUpdate$('success');
51+
52+
await expect(
53+
collect(update$.pipe(handleOptimizerCompletion(config(true))))
54+
).rejects.toThrowErrorMatchingInlineSnapshot(
55+
`"optimizer unexpectedly completed when in watch mode"`
56+
);
57+
});
58+
59+
it('errors if the optimizer completes in phase "issue"', async () => {
60+
const update$ = createUpdate$('issue');
61+
62+
await expect(
63+
collect(update$.pipe(handleOptimizerCompletion(config())))
64+
).rejects.toThrowErrorMatchingInlineSnapshot(`"webpack issue"`);
65+
});
66+
67+
it('errors if the optimizer completes in phase "initializing"', async () => {
68+
const update$ = createUpdate$('initializing');
69+
70+
await expect(
71+
collect(update$.pipe(handleOptimizerCompletion(config())))
72+
).rejects.toThrowErrorMatchingInlineSnapshot(
73+
`"optimizer unexpectedly exit in phase \\"initializing\\""`
74+
);
75+
});
76+
77+
it('errors if the optimizer completes in phase "reallocating"', async () => {
78+
const update$ = createUpdate$('reallocating');
79+
80+
await expect(
81+
collect(update$.pipe(handleOptimizerCompletion(config())))
82+
).rejects.toThrowErrorMatchingInlineSnapshot(
83+
`"optimizer unexpectedly exit in phase \\"reallocating\\""`
84+
);
85+
});
86+
87+
it('errors if the optimizer completes in phase "running"', async () => {
88+
const update$ = createUpdate$('running');
89+
90+
await expect(
91+
collect(update$.pipe(handleOptimizerCompletion(config())))
92+
).rejects.toThrowErrorMatchingInlineSnapshot(
93+
`"optimizer unexpectedly exit in phase \\"running\\""`
94+
);
95+
});
96+
97+
it('passes through errors on the source stream', async () => {
98+
const error = new Error('foo');
99+
const update$ = Rx.throwError(error);
100+
101+
await expect(collect(update$.pipe(handleOptimizerCompletion(config())))).rejects.toThrowError(
102+
error
103+
);
104+
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import * as Rx from 'rxjs';
21+
import { tap } from 'rxjs/operators';
22+
import { createFailError } from '@kbn/dev-utils';
23+
24+
import { pipeClosure, Update } from '../common';
25+
26+
import { OptimizerState } from './optimizer_reducer';
27+
import { OptimizerConfig } from './optimizer_config';
28+
29+
export function handleOptimizerCompletion(config: OptimizerConfig) {
30+
return pipeClosure((source$: Rx.Observable<Update<any, OptimizerState>>) => {
31+
let prevState: OptimizerState | undefined;
32+
33+
return source$.pipe(
34+
tap({
35+
next: update => {
36+
prevState = update.state;
37+
},
38+
complete: () => {
39+
if (config.watch) {
40+
throw new Error('optimizer unexpectedly completed when in watch mode');
41+
}
42+
43+
if (prevState?.phase === 'success') {
44+
return;
45+
}
46+
47+
if (prevState?.phase === 'issue') {
48+
throw createFailError('webpack issue');
49+
}
50+
51+
throw new Error(`optimizer unexpectedly exit in phase "${prevState?.phase}"`);
52+
},
53+
})
54+
);
55+
});
56+
}

packages/kbn-optimizer/src/optimizer/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ export * from './cache_keys';
2424
export * from './watch_bundles_for_changes';
2525
export * from './run_workers';
2626
export * from './bundle_cache';
27+
export * from './handle_optimizer_completion';

packages/kbn-optimizer/src/run_optimizer.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
runWorkers,
3333
OptimizerInitializedEvent,
3434
createOptimizerReducer,
35+
handleOptimizerCompletion,
3536
} from './optimizer';
3637

3738
export type OptimizerUpdate = Update<OptimizerEvent, OptimizerState>;
@@ -77,6 +78,7 @@ export function runOptimizer(config: OptimizerConfig) {
7778
},
7879
createOptimizerReducer(config)
7980
);
80-
})
81+
}),
82+
handleOptimizerCompletion(config)
8183
);
8284
}

0 commit comments

Comments
 (0)