expression_evaluation_web_test.dart 9.56 KB
Newer Older
1 2 3 4 5 6 7
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:file/file.dart';
import 'package:vm_service/vm_service.dart';

8 9 10 11
import '../integration.shard/test_data/basic_project.dart';
import '../integration.shard/test_data/tests_project.dart';
import '../integration.shard/test_driver.dart';
import '../integration.shard/test_utils.dart';
12 13
import '../src/common.dart';

14 15 16
void main() {
  group('Flutter run for web', () {
    final BasicProject project = BasicProject();
17 18
    late Directory tempDir;
    late FlutterRunTestDriver flutter;
19 20 21 22 23

    setUp(() async {
      tempDir = createResolvedTempDirectorySync('run_expression_eval_test.');
      await project.setUpIn(tempDir);
      flutter = FlutterRunTestDriver(tempDir);
24 25 26
      flutter.stdout.listen((String line) {
        expect(line, isNot(contains('Unresolved uri:')));
      });
27 28 29 30 31 32 33
    });

    tearDown(() async {
      await flutter.stop();
      tryToDelete(tempDir);
    });

34
    Future<void> start({required bool expressionEvaluation}) async {
35 36 37 38 39
      // The non-test project has a loop around its breakpoints.
      // No need to start paused as all breakpoint would be eventually reached.
      await flutter.run(
        withDebugger: true, chrome: true,
        expressionEvaluation: expressionEvaluation,
40
        additionalCommandArgs: <String>['--verbose', '--web-renderer=html']);
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
    }

    Future<void> breakInBuildMethod(FlutterTestDriver flutter) async {
      await flutter.breakAt(
        project.buildMethodBreakpointUri,
        project.buildMethodBreakpointLine,
      );
    }

    Future<void> breakInTopLevelFunction(FlutterTestDriver flutter) async {
      await flutter.breakAt(
        project.topLevelFunctionBreakpointUri,
        project.topLevelFunctionBreakpointLine,
      );
    }

    testWithoutContext('cannot evaluate expression if feature is disabled', () async {
      await start(expressionEvaluation: false);
      await breakInTopLevelFunction(flutter);
      await failToEvaluateExpression(flutter);
    });

    testWithoutContext('shows no native javascript objects in static scope', () async {
      await start(expressionEvaluation: true);
      await breakInTopLevelFunction(flutter);
      await checkStaticScope(flutter);
    });

    testWithoutContext('can handle compilation errors', () async {
      await start(expressionEvaluation: true);
      await breakInTopLevelFunction(flutter);
      await evaluateErrorExpressions(flutter);
    });

    testWithoutContext('can evaluate trivial expressions in top level function', () async {
      await start(expressionEvaluation: true);
      await breakInTopLevelFunction(flutter);
      await evaluateTrivialExpressions(flutter);
    });

    testWithoutContext('can evaluate trivial expressions in build method', () async {
      await start(expressionEvaluation: true);
      await breakInBuildMethod(flutter);
      await evaluateTrivialExpressions(flutter);
    });

    testWithoutContext('can evaluate complex expressions in top level function', () async {
      await start(expressionEvaluation: true);
      await breakInTopLevelFunction(flutter);
      await evaluateComplexExpressions(flutter);
    });

    testWithoutContext('can evaluate complex expressions in build method', () async {
      await start(expressionEvaluation: true);
      await breakInBuildMethod(flutter);
      await evaluateComplexExpressions(flutter);
    });

    testWithoutContext('can evaluate trivial expressions in library without pause', () async {
      await start(expressionEvaluation: true);
      await evaluateTrivialExpressionsInLibrary(flutter);
    });

    testWithoutContext('can evaluate complex expressions in library without pause', () async {
      await start(expressionEvaluation: true);
      await evaluateComplexExpressionsInLibrary(flutter);
    });
108 109 110 111 112

    testWithoutContext('evaluated expression includes web library environment defines', () async {
      await start(expressionEvaluation: true);
      await evaluateWebLibraryBooleanFromEnvironmentInLibrary(flutter);
    });
113
  });
114

115 116
  group('Flutter test for web', () {
    final TestsProject project = TestsProject();
117 118
    late Directory tempDir;
    late FlutterRunTestDriver flutter;
119 120 121 122 123 124 125 126 127 128 129 130

    setUp(() async {
      tempDir = createResolvedTempDirectorySync('run_expression_eval_test.');
      await project.setUpIn(tempDir);
      flutter = FlutterRunTestDriver(tempDir);
    });

    tearDown(() async {
      await flutter.stop();
      tryToDelete(tempDir);
    });

131
    Future<Isolate?> breakInMethod(FlutterTestDriver flutter) async {
132 133 134 135
      await flutter.addBreakpoint(
        project.breakpointAppUri,
        project.breakpointLine,
      );
136
      return flutter.resume(waitForNextPause: true);
137 138
    }

139
    Future<void> startPaused({required bool expressionEvaluation}) {
140 141 142 143 144 145 146
      // The test project does not have a loop around its breakpoints.
      // Start paused so we can set a breakpoint before passing it
      // in the execution.
      return flutter.run(
        withDebugger: true, chrome: true,
        expressionEvaluation: expressionEvaluation,
        startPaused: true, script: project.testFilePath,
147
        additionalCommandArgs: <String>['--verbose', '--web-renderer=html']);
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
    }

    testWithoutContext('cannot evaluate expressions if feature is disabled', () async {
      await startPaused(expressionEvaluation: false);
      await breakInMethod(flutter);
      await failToEvaluateExpression(flutter);
    });

    testWithoutContext('can evaluate trivial expressions in a test', () async {
      await startPaused(expressionEvaluation: true);
      await breakInMethod(flutter);
      await evaluateTrivialExpressions(flutter);
    });

    testWithoutContext('can evaluate complex expressions in a test', () async {
      await startPaused(expressionEvaluation: true);
      await breakInMethod(flutter);
      await evaluateComplexExpressions(flutter);
    });

    testWithoutContext('can evaluate trivial expressions in library without pause', () async {
      await startPaused(expressionEvaluation: true);
      await evaluateTrivialExpressionsInLibrary(flutter);
    });

    testWithoutContext('can evaluate complex expressions in library without pause', () async {
      await startPaused(expressionEvaluation: true);
      await evaluateComplexExpressionsInLibrary(flutter);
    });
177 178 179 180
    testWithoutContext('evaluated expression includes web library environment defines', () async {
      await startPaused(expressionEvaluation: true);
      await evaluateWebLibraryBooleanFromEnvironmentInLibrary(flutter);
    });
181
  });
182
}
183

184
Future<void> failToEvaluateExpression(FlutterTestDriver flutter) async {
185
  await expectLater(
186
    flutter.evaluateInFrame('"test"'),
187 188 189 190 191 192
    throwsA(isA<RPCError>().having(
      (RPCError error) => error.message,
      'message',
      contains('Expression evaluation is not supported for this configuration'),
    )),
  );
193 194
}

195 196 197 198
Future<void> checkStaticScope(FlutterTestDriver flutter) async {
  final Frame res = await flutter.getTopStackFrame();
  expect(res.vars, equals(<BoundVariable>[]));
}
199 200 201

Future<void> evaluateErrorExpressions(FlutterTestDriver flutter) async {
  final ObjRef res = await flutter.evaluateInFrame('typo');
202
  expectError(res, 'CompilationError:');
203 204 205
}

Future<void> evaluateTrivialExpressions(FlutterTestDriver flutter) async {
206
  ObjRef res;
207 208

  res = await flutter.evaluateInFrame('"test"');
209
  expectInstance(res, InstanceKind.kString, 'test');
210 211

  res = await flutter.evaluateInFrame('1');
212
  expectInstance(res, InstanceKind.kDouble, 1.toString());
213 214

  res = await flutter.evaluateInFrame('true');
215
  expectInstance(res, InstanceKind.kBool, true.toString());
216 217 218
}

Future<void> evaluateComplexExpressions(FlutterTestDriver flutter) async {
219 220 221 222
  final ObjRef res = await flutter.evaluateInFrame('new DateTime.now().year');
  expectInstance(res, InstanceKind.kDouble, DateTime.now().year.toString());
}

223 224
Future<void> evaluateTrivialExpressionsInLibrary(FlutterTestDriver flutter) async {
  final LibraryRef library = await getRootLibrary(flutter);
225
  final ObjRef res = await flutter.evaluate(library.id!, '"test"');
226 227 228 229 230
  expectInstance(res, InstanceKind.kString, 'test');
}

Future<void> evaluateComplexExpressionsInLibrary(FlutterTestDriver flutter) async {
  final LibraryRef library = await getRootLibrary(flutter);
231
  final ObjRef res = await flutter.evaluate(library.id!, 'new DateTime.now().year');
232 233 234
  expectInstance(res, InstanceKind.kDouble, DateTime.now().year.toString());
}

235 236
Future<void> evaluateWebLibraryBooleanFromEnvironmentInLibrary(FlutterTestDriver flutter) async {
  final LibraryRef library = await getRootLibrary(flutter);
237
  final ObjRef res = await flutter.evaluate(library.id!, 'const bool.fromEnvironment("dart.library.html")');
238 239 240
  expectInstance(res, InstanceKind.kBool, true.toString());
}

241 242 243 244 245 246
Future<LibraryRef> getRootLibrary(FlutterTestDriver flutter) async {
  // `isolate.rootLib` returns incorrect library, so find the
  // entrypoint manually here instead.
  //
  // Issue: https://github.com/dart-lang/sdk/issues/44760
  final Isolate isolate = await flutter.getFlutterIsolate();
247 248
  return isolate.libraries!
    .firstWhere((LibraryRef l) => l.uri!.contains('org-dartlang-app'));
249 250
}

251 252 253 254 255
void expectInstance(ObjRef result, String kind, String message) {
  expect(result,
    const TypeMatcher<InstanceRef>()
      .having((InstanceRef instance) => instance.kind, 'kind', kind)
      .having((InstanceRef instance) => instance.valueAsString, 'valueAsString', message));
256 257
}

258 259 260
void expectError(ObjRef result, String message) {
  expect(result,
    const TypeMatcher<ErrorRef>()
261
      .having((ErrorRef instance) => instance.message, 'message', contains(message)));
262
}