Skip to content

Commit cf0323f

Browse files
authored
test(dart_frog_prod_server): post_gen tests (#206)
1 parent 68701be commit cf0323f

File tree

3 files changed

+269
-15
lines changed

3 files changed

+269
-15
lines changed

.github/workflows/dart_frog_prod_server.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ jobs:
1818
with:
1919
working_directory: bricks/dart_frog_prod_server/hooks
2020
analyze_directories: .
21-
report_on: pre_gen.dart
21+
report_on: "pre_gen.dart,post_gen.dart"
Lines changed: 80 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,34 @@
1-
import 'dart:io';
1+
import 'dart:async';
2+
import 'dart:io' as io;
23

34
import 'package:mason/mason.dart';
45
import 'package:path/path.dart' as path;
56

6-
Future<void> run(HookContext context) async {
7-
final progress = context.logger.progress('Installing dependencies');
8-
final buildDirectoryPath = path.join(Directory.current.path, 'build');
9-
final result = await Process.run(
10-
'dart',
11-
['pub', 'get'],
7+
typedef ProcessRunner = Future<io.ProcessResult> Function(
8+
String executable,
9+
List<String> arguments, {
10+
String workingDirectory,
11+
bool runInShell,
12+
});
13+
14+
Future<void> run(HookContext context) => postGen(context);
15+
16+
Future<void> postGen(
17+
HookContext context, {
18+
io.Directory? directory,
19+
ProcessRunner runProcess = io.Process.run,
20+
void Function(int exitCode)? exit,
21+
}) async {
22+
final _exit = exit ?? ExitOverrides.current?.exit ?? io.exit;
23+
final _directory = directory ?? io.Directory.current;
24+
final buildDirectoryPath = path.join(_directory.path, 'build');
25+
26+
await dartPubGet(
27+
context,
1228
workingDirectory: buildDirectoryPath,
13-
runInShell: true,
29+
runProcess: runProcess,
30+
exit: _exit,
1431
);
15-
progress.complete();
16-
17-
if (result.exitCode != 0) {
18-
context.logger.err('${result.stderr}');
19-
exit(result.exitCode);
20-
}
2132

2233
final relativeBuildPath = path.relative(buildDirectoryPath);
2334
context.logger
@@ -30,3 +41,58 @@ Future<void> run(HookContext context) async {
3041
'''${lightCyan.wrap('dart ${path.join(relativeBuildPath, 'bin', 'server.dart')}')}''',
3142
);
3243
}
44+
45+
Future<void> dartPubGet(
46+
HookContext context, {
47+
required String workingDirectory,
48+
required ProcessRunner runProcess,
49+
required void Function(int exitCode) exit,
50+
}) async {
51+
final progress = context.logger.progress('Installing dependencies');
52+
try {
53+
final result = await runProcess(
54+
'dart',
55+
['pub', 'get'],
56+
workingDirectory: workingDirectory,
57+
runInShell: true,
58+
);
59+
progress.complete();
60+
61+
if (result.exitCode != 0) {
62+
context.logger.err('${result.stderr}');
63+
return exit(result.exitCode);
64+
}
65+
} on io.ProcessException catch (error) {
66+
context.logger.err(error.message);
67+
return exit(error.errorCode);
68+
}
69+
}
70+
71+
const _asyncRunZoned = runZoned;
72+
73+
abstract class ExitOverrides {
74+
static final _token = Object();
75+
76+
static ExitOverrides? get current {
77+
return Zone.current[_token] as ExitOverrides?;
78+
}
79+
80+
static R runZoned<R>(R Function() body, {void Function(int)? exit}) {
81+
final overrides = _ExitOverridesScope(exit);
82+
return _asyncRunZoned(body, zoneValues: {_token: overrides});
83+
}
84+
85+
void Function(int exitCode) get exit => io.exit;
86+
}
87+
88+
class _ExitOverridesScope extends ExitOverrides {
89+
_ExitOverridesScope(this._exit);
90+
91+
final ExitOverrides? _previous = ExitOverrides.current;
92+
final void Function(int exitCode)? _exit;
93+
94+
@override
95+
void Function(int exitCode) get exit {
96+
return _exit ?? _previous?.exit ?? super.exit;
97+
}
98+
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import 'dart:io';
2+
3+
import 'package:mason/mason.dart';
4+
import 'package:mocktail/mocktail.dart';
5+
import 'package:path/path.dart' as path;
6+
import 'package:test/test.dart';
7+
8+
import '../post_gen.dart';
9+
10+
class _FakeHookContext extends Fake implements HookContext {
11+
_FakeHookContext({Logger? logger}) : _logger = logger ?? _MockLogger();
12+
13+
final Logger _logger;
14+
15+
var _vars = <String, dynamic>{};
16+
17+
@override
18+
Map<String, dynamic> get vars => _vars;
19+
20+
@override
21+
set vars(Map<String, dynamic> value) => _vars = value;
22+
23+
@override
24+
Logger get logger => _logger;
25+
}
26+
27+
class _MockLogger extends Mock implements Logger {}
28+
29+
class _MockProgress extends Mock implements Progress {}
30+
31+
class _MockProcessResult extends Mock implements ProcessResult {}
32+
33+
void main() {
34+
group('postGen', () {
35+
late HookContext context;
36+
late Logger logger;
37+
38+
setUp(() {
39+
logger = _MockLogger();
40+
context = _FakeHookContext(logger: logger);
41+
42+
when(() => logger.progress(any())).thenReturn(_MockProgress());
43+
});
44+
45+
test('run completes', () {
46+
expect(
47+
ExitOverrides.runZoned(
48+
() => run(_FakeHookContext(logger: logger)),
49+
exit: (_) {},
50+
),
51+
completes,
52+
);
53+
});
54+
55+
test('runs dart pub get and outputs next steps', () async {
56+
var processRunnerCallCount = 0;
57+
final exitCalls = <int>[];
58+
final result = _MockProcessResult();
59+
when(() => result.exitCode).thenReturn(ExitCode.success.code);
60+
await postGen(
61+
context,
62+
runProcess: (
63+
executable,
64+
args, {
65+
String? workingDirectory,
66+
bool? runInShell,
67+
}) async {
68+
processRunnerCallCount++;
69+
expect(executable, equals('dart'));
70+
expect(args, equals(['pub', 'get']));
71+
expect(
72+
workingDirectory,
73+
equals(path.join(Directory.current.path, 'build')),
74+
);
75+
expect(runInShell, isTrue);
76+
return result;
77+
},
78+
exit: exitCalls.add,
79+
);
80+
expect(processRunnerCallCount, equals(1));
81+
expect(exitCalls, isEmpty);
82+
verify(() => logger.success('Created a production build!')).called(1);
83+
verify(
84+
() => logger.info('Start the production server by running:'),
85+
).called(1);
86+
verify(
87+
() => logger.info('${lightCyan.wrap('dart build/bin/server.dart')}'),
88+
).called(1);
89+
verifyNever(() => logger.err(any()));
90+
});
91+
92+
group('dartPubGet', () {
93+
test('completes when process succeeds', () async {
94+
final exitCalls = <int>[];
95+
final result = _MockProcessResult();
96+
when(() => result.exitCode).thenReturn(ExitCode.success.code);
97+
await dartPubGet(
98+
context,
99+
workingDirectory: '.',
100+
runProcess: (
101+
executable,
102+
args, {
103+
String? workingDirectory,
104+
bool? runInShell,
105+
}) async {
106+
expect(executable, equals('dart'));
107+
expect(args, equals(['pub', 'get']));
108+
expect(workingDirectory, equals('.'));
109+
expect(runInShell, isTrue);
110+
return result;
111+
},
112+
exit: exitCalls.add,
113+
);
114+
expect(exitCalls, isEmpty);
115+
verifyNever(() => logger.err(any()));
116+
});
117+
118+
test('exits when process fails', () async {
119+
const error = 'oops something went wrong';
120+
final exitCalls = <int>[];
121+
final result = _MockProcessResult();
122+
final code = ExitCode.software.code;
123+
when(() => result.exitCode).thenReturn(code);
124+
when(() => result.stderr).thenReturn(error);
125+
await dartPubGet(
126+
context,
127+
workingDirectory: '.',
128+
runProcess: (
129+
executable,
130+
args, {
131+
String? workingDirectory,
132+
bool? runInShell,
133+
}) async {
134+
return result;
135+
},
136+
exit: exitCalls.add,
137+
);
138+
expect(exitCalls, equals([code]));
139+
verify(() => logger.err(error)).called(1);
140+
});
141+
142+
test('exits when ProcessException occurs', () async {
143+
const error = 'oops something went wrong';
144+
final exitCalls = <int>[];
145+
final result = _MockProcessResult();
146+
final code = ExitCode.software.code;
147+
when(() => result.exitCode).thenReturn(code);
148+
when(() => result.stderr).thenReturn(error);
149+
await dartPubGet(
150+
context,
151+
workingDirectory: '.',
152+
runProcess: (
153+
executable,
154+
args, {
155+
String? workingDirectory,
156+
bool? runInShell,
157+
}) async {
158+
throw ProcessException('dart', ['pub', 'get'], error, code);
159+
},
160+
exit: exitCalls.add,
161+
);
162+
expect(exitCalls, equals([code]));
163+
verify(() => logger.err(error)).called(1);
164+
});
165+
});
166+
});
167+
168+
group('ExitOverrides', () {
169+
group('runZoned', () {
170+
test('uses default exit when not specified', () {
171+
ExitOverrides.runZoned(() {
172+
final overrides = ExitOverrides.current;
173+
expect(overrides!.exit, equals(exit));
174+
});
175+
});
176+
177+
test('uses custom exit when specified', () {
178+
ExitOverrides.runZoned(
179+
() {
180+
final overrides = ExitOverrides.current;
181+
expect(overrides!.exit, isNot(equals(exit)));
182+
},
183+
exit: (_) {},
184+
);
185+
});
186+
});
187+
});
188+
}

0 commit comments

Comments
 (0)