This is an automated email from the ASF dual-hosted git repository.
pabloem pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git
The following commit(s) were added to refs/heads/master by this push:
new 6865eefac56 [Playground] Refuse RunCode requests if NUM_PARALLEL_JOBS
is exceeded (#26737)
6865eefac56 is described below
commit 6865eefac5633198c71fd1ec75b514e8a390d002
Author: Timur Sultanov <[email protected]>
AuthorDate: Fri May 26 21:05:07 2023 +0400
[Playground] Refuse RunCode requests if NUM_PARALLEL_JOBS is exceeded
(#26737)
* Refuse RunCode requests if NUM_PARALLEL_JOBS is exceeded
* Retry running if backend was overloaded
* Fix the embedded_run frontend integration test
* Ignore copy button when a snippet has not loaded yet
* Change run attempts and interval
---------
Co-authored-by: Alexey Inkin <[email protected]>
Co-authored-by: Alexey Inkin <[email protected]>
---
learning/tour-of-beam/frontend/lib/locator.dart | 1 -
.../frontend/lib/pages/tour/state.dart | 2 +-
playground/backend/cmd/server/controller.go | 6 +
playground/backend/internal/errors/grpc_errors.go | 6 +
playground/backend/internal/utils/system_utils.go | 6 +-
.../backend/internal/utils/system_utils_test.go | 12 +-
playground/frontend/lib/controllers/factories.dart | 2 +-
playground/frontend/lib/locator.dart | 3 -
.../widgets/embedded_appbar_title.dart | 4 +-
.../lib/playground_components.dart | 1 -
.../lib/src/controllers/code_runner.dart | 333 ++++++++++--
.../lib/src/controllers/playground_controller.dart | 6 +-
.../repositories/code_client/grpc_code_client.dart | 20 +-
.../lib/src/repositories/code_repository.dart | 241 ---------
.../src/repositories/models/run_code_error.dart | 6 +
.../src/repositories/models/run_code_result.dart | 26 +-
.../lib/src/widgets/run_or_cancel_button.dart | 11 +-
.../test/src/controllers/code_runner_test.dart | 258 ++++++++++
.../src/controllers/code_runner_test.mocks.dart | 564 +++++++++++++++++++++
.../src/repositories/code_repository_test.dart | 389 --------------
.../repositories/code_repository_test.mocks.dart | 249 ---------
.../lib/src/widget_tester.dart | 6 +-
22 files changed, 1186 insertions(+), 966 deletions(-)
diff --git a/learning/tour-of-beam/frontend/lib/locator.dart
b/learning/tour-of-beam/frontend/lib/locator.dart
index 46095e92948..fedfa528c05 100644
--- a/learning/tour-of-beam/frontend/lib/locator.dart
+++ b/learning/tour-of-beam/frontend/lib/locator.dart
@@ -57,7 +57,6 @@ Future<void> _initializeRepositories() async {
final exampleClient = GrpcExampleClient(url: routerUrl);
GetIt.instance.registerSingleton<CodeClient>(codeClient);
- GetIt.instance.registerSingleton(CodeRepository(client: codeClient));
GetIt.instance.registerSingleton<ExampleClient>(exampleClient);
GetIt.instance.registerSingleton(ExampleRepository(client: exampleClient));
}
diff --git a/learning/tour-of-beam/frontend/lib/pages/tour/state.dart
b/learning/tour-of-beam/frontend/lib/pages/tour/state.dart
index a228d3a0621..00d2f2fcddc 100644
--- a/learning/tour-of-beam/frontend/lib/pages/tour/state.dart
+++ b/learning/tour-of-beam/frontend/lib/pages/tour/state.dart
@@ -351,7 +351,7 @@ class TourNotifier extends ChangeNotifier with
PageStateMixin<void> {
static PlaygroundController _createPlaygroundController(String initialSdkId)
{
final playgroundController = PlaygroundController(
- codeRepository: GetIt.instance.get<CodeRepository>(),
+ codeClient: GetIt.instance.get<CodeClient>(),
exampleCache: ExampleCache(
exampleRepository: GetIt.instance.get<ExampleRepository>(),
),
diff --git a/playground/backend/cmd/server/controller.go
b/playground/backend/cmd/server/controller.go
index 606e5fd4dcf..f788f2e613f 100644
--- a/playground/backend/cmd/server/controller.go
+++ b/playground/backend/cmd/server/controller.go
@@ -69,11 +69,17 @@ type playgroundController struct {
// RunCode is running code from requests using a particular SDK
// - In case of incorrect sdk returns codes.InvalidArgument
+// - In case of exceeded number of parallel jobs returns
codes.ResourceExhausted
// - In case of error during preparing files/folders returns codes.Internal
// - In case of no errors saves playground.Status_STATUS_EXECUTING as
cache.Status into cache and sets expiration time
// for all cache values which will be saved into cache during processing
received code.
// Returns id of code processing (pipelineId)
func (controller *playgroundController) RunCode(ctx context.Context, info
*pb.RunCodeRequest) (*pb.RunCodeResponse, error) {
+ // check if we can take a new RunCode request
+ if
!utils.CheckNumOfTheParallelJobs(controller.env.ApplicationEnvs.WorkingDir(),
controller.env.BeamSdkEnvs.NumOfParallelJobs()) {
+ logger.Warnf("RunCode(): number of parallel jobs is exceeded\n")
+ return nil, cerrors.ResourceExhaustedError("Error during
preparing", "Number of parallel jobs is exceeded")
+ }
// check for correct sdk
if info.Sdk != controller.env.BeamSdkEnvs.ApacheBeamSdk {
logger.Errorf("RunCode(): request contains incorrect sdk:
%s\n", info.Sdk)
diff --git a/playground/backend/internal/errors/grpc_errors.go
b/playground/backend/internal/errors/grpc_errors.go
index b1c744f6068..59b80267192 100644
--- a/playground/backend/internal/errors/grpc_errors.go
+++ b/playground/backend/internal/errors/grpc_errors.go
@@ -38,3 +38,9 @@ func InternalError(title string, formatMessage string, args
...interface{}) erro
message := fmt.Sprintf(formatMessage, args...)
return status.Errorf(codes.Internal, "%s: %s", title, message)
}
+
+// ResourceExhaustedError returns error with ResourceExhausted code error and
message like "title: message"
+func ResourceExhaustedError(title string, formatMessage string, args
...interface{}) error {
+ message := fmt.Sprintf(formatMessage, args...)
+ return status.Errorf(codes.ResourceExhausted, "%s: %s", title, message)
+}
diff --git a/playground/backend/internal/utils/system_utils.go
b/playground/backend/internal/utils/system_utils.go
index acc2ca29fb2..dd631c1b5cd 100644
--- a/playground/backend/internal/utils/system_utils.go
+++ b/playground/backend/internal/utils/system_utils.go
@@ -47,7 +47,7 @@ func GetLivenessFunction() func(writer http.ResponseWriter,
request *http.Reques
// GetReadinessFunction returns the function that checks the readiness of the
server to process a new code processing request
func GetReadinessFunction(envs *environment.Environment) func(writer
http.ResponseWriter, request *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
- if checkNumOfTheParallelJobs(envs.ApplicationEnvs.WorkingDir(),
envs.BeamSdkEnvs.NumOfParallelJobs()) {
+ if CheckNumOfTheParallelJobs(envs.ApplicationEnvs.WorkingDir(),
envs.BeamSdkEnvs.NumOfParallelJobs()) {
writer.WriteHeader(http.StatusOK)
} else {
writer.WriteHeader(http.StatusLocked)
@@ -55,13 +55,13 @@ func GetReadinessFunction(envs *environment.Environment)
func(writer http.Respon
}
}
-// checkNumOfTheParallelJobs checks the number of currently working code
executions.
+// CheckNumOfTheParallelJobs checks the number of currently working code
executions.
//
// It counts by the number of the
/path/to/workingDir/executableFiles/{pipelineId} folders.
//
// If it is equals or more than numOfParallelJobs, then returns false.
// If it is less than numOfParallelJobs, then returns true.
-func checkNumOfTheParallelJobs(workingDir string, numOfParallelJobs int) bool {
+func CheckNumOfTheParallelJobs(workingDir string, numOfParallelJobs int) bool {
baseFileFolder := filepath.Join(workingDir, executableFiles)
_, err := os.Stat(baseFileFolder)
if os.IsNotExist(err) {
diff --git a/playground/backend/internal/utils/system_utils_test.go
b/playground/backend/internal/utils/system_utils_test.go
index 1817ba01254..a68fb8a8e58 100644
--- a/playground/backend/internal/utils/system_utils_test.go
+++ b/playground/backend/internal/utils/system_utils_test.go
@@ -58,7 +58,7 @@ func Test_checkNumOfTheParallelJobs(t *testing.T) {
want bool
}{
{
- // Test case with calling checkNumOfTheParallelJobs
when there is no code processing folders.
+ // Test case with calling CheckNumOfTheParallelJobs
when there is no code processing folders.
// As a result, want to receive true
name: "There is no code processing folder",
args: args{
@@ -69,7 +69,7 @@ func Test_checkNumOfTheParallelJobs(t *testing.T) {
want: true,
},
{
- // Test case with calling checkNumOfTheParallelJobs
when there is one code processing folder.
+ // Test case with calling CheckNumOfTheParallelJobs
when there is one code processing folder.
// As a result, want to receive true
name: "Less than needed",
args: args{
@@ -85,7 +85,7 @@ func Test_checkNumOfTheParallelJobs(t *testing.T) {
want: true,
},
{
- // Test case with calling checkNumOfTheParallelJobs
when the number of the code processing folders is equals numOfParallelJobs.
+ // Test case with calling CheckNumOfTheParallelJobs
when the number of the code processing folders is equals numOfParallelJobs.
// As a result, want to receive false
name: "There are enough code processing folders",
args: args{
@@ -101,7 +101,7 @@ func Test_checkNumOfTheParallelJobs(t *testing.T) {
want: false,
},
{
- // Test case with calling checkNumOfTheParallelJobs
when the number of the code processing folders is more than numOfParallelJobs.
+ // Test case with calling CheckNumOfTheParallelJobs
when the number of the code processing folders is more than numOfParallelJobs.
// As a result, want to receive false
name: "More than needed",
args: args{
@@ -120,8 +120,8 @@ func Test_checkNumOfTheParallelJobs(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.prepareFunc()
- if got := checkNumOfTheParallelJobs(tt.args.workingDir,
tt.args.numOfParallelJobs); got != tt.want {
- t.Errorf("checkNumOfTheParallelJobs() = %v,
want %v", got, tt.want)
+ if got := CheckNumOfTheParallelJobs(tt.args.workingDir,
tt.args.numOfParallelJobs); got != tt.want {
+ t.Errorf("CheckNumOfTheParallelJobs() = %v,
want %v", got, tt.want)
}
os.RemoveAll(executableFiles)
})
diff --git a/playground/frontend/lib/controllers/factories.dart
b/playground/frontend/lib/controllers/factories.dart
index 92863c18bb7..3a8e2388790 100644
--- a/playground/frontend/lib/controllers/factories.dart
+++ b/playground/frontend/lib/controllers/factories.dart
@@ -36,7 +36,7 @@ PlaygroundController createPlaygroundController(
final controller = PlaygroundController(
examplesLoader: ExamplesLoader(),
exampleCache: exampleCache,
- codeRepository: GetIt.instance.get<CodeRepository>(),
+ codeClient: GetIt.instance.get<CodeClient>(),
);
unawaited(_loadExamples(controller, descriptor));
diff --git a/playground/frontend/lib/locator.dart
b/playground/frontend/lib/locator.dart
index 8115e2cd88b..21496607b6e 100644
--- a/playground/frontend/lib/locator.dart
+++ b/playground/frontend/lib/locator.dart
@@ -42,9 +42,6 @@ Future<void> _initializeRepositories() async {
);
GetIt.instance.registerSingleton<CodeClient>(codeClient);
- GetIt.instance.registerSingleton(CodeRepository(
- client: codeClient,
- ));
final exampleClient = GrpcExampleClient(url: routerUrl);
GetIt.instance.registerSingleton<ExampleClient>(exampleClient);
diff --git
a/playground/frontend/lib/pages/embedded_playground/widgets/embedded_appbar_title.dart
b/playground/frontend/lib/pages/embedded_playground/widgets/embedded_appbar_title.dart
index 75443f03b5a..09b4370f72b 100644
---
a/playground/frontend/lib/pages/embedded_playground/widgets/embedded_appbar_title.dart
+++
b/playground/frontend/lib/pages/embedded_playground/widgets/embedded_appbar_title.dart
@@ -44,7 +44,9 @@ class EmbeddedAppBarTitle extends StatelessWidget {
icon: SvgPicture.asset(Assets.copy),
onPressed: () {
final source = controller.source;
- Clipboard.setData(ClipboardData(text: source));
+ if (source != null) {
+ Clipboard.setData(ClipboardData(text: source));
+ }
},
),
],
diff --git
a/playground/frontend/playground_components/lib/playground_components.dart
b/playground/frontend/playground_components/lib/playground_components.dart
index 6599def9d8d..06959a14d96 100644
--- a/playground/frontend/playground_components/lib/playground_components.dart
+++ b/playground/frontend/playground_components/lib/playground_components.dart
@@ -64,7 +64,6 @@ export 'src/playground_components.dart';
export 'src/repositories/backend_urls.dart';
export 'src/repositories/code_client/code_client.dart';
export 'src/repositories/code_client/grpc_code_client.dart';
-export 'src/repositories/code_repository.dart';
export 'src/repositories/example_client/example_client.dart';
export 'src/repositories/example_client/grpc_example_client.dart';
export 'src/repositories/example_repository.dart';
diff --git
a/playground/frontend/playground_components/lib/src/controllers/code_runner.dart
b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart
index ff89d1c1a2e..68b357b3632 100644
---
a/playground/frontend/playground_components/lib/src/controllers/code_runner.dart
+++
b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart
@@ -25,14 +25,28 @@ import 'package:flutter/material.dart';
import '../../playground_components.dart';
import '../enums/unread_entry.dart';
+import '../repositories/models/output_response.dart';
+import '../repositories/models/run_code_error.dart';
import '../repositories/models/run_code_request.dart';
+import '../repositories/models/run_code_response.dart';
import '../repositories/models/run_code_result.dart';
import '../util/connectivity_result.dart';
import 'snippet_editing_controller.dart';
import 'unread_controller.dart';
+const kTimeoutErrorText =
+ 'Pipeline exceeded Playground execution timeout and was terminated. '
+ 'We recommend installing Apache Beam '
+ 'https://beam.apache.org/get-started/downloads/ '
+ 'to try examples without timeout limitation.';
+const kUnknownErrorText =
+ 'Something went wrong. Please try again later or create a GitHub issue';
+const kProcessingStartedText = 'The processing has been started\n';
+const kProcessingStartedOptionsText =
+ 'The processing has been started with the pipeline options: ';
+
class CodeRunner extends ChangeNotifier {
- final CodeRepository? _codeRepository;
+ final CodeClient? codeClient;
final ValueGetter<SnippetEditingController?> _snippetEditingControllerGetter;
SnippetEditingController? snippetEditingController;
final unreadController = UnreadController();
@@ -40,9 +54,8 @@ class CodeRunner extends ChangeNotifier {
CodeRunner({
required ValueGetter<SnippetEditingController?>
snippetEditingControllerGetter,
- CodeRepository? codeRepository,
- }) : _codeRepository = codeRepository,
- _snippetEditingControllerGetter = snippetEditingControllerGetter;
+ this.codeClient,
+ }) : _snippetEditingControllerGetter = snippetEditingControllerGetter;
RunCodeResult? _result;
StreamSubscription<RunCodeResult>? _runSubscription;
@@ -64,12 +77,17 @@ class CodeRunner extends ChangeNotifier {
_snippetEditingControllerGetter()?.pipelineOptions;
RunCodeResult? get result => _result;
+
DateTime? get runStartDate => _runStartDate;
+
DateTime? get runStopDate => _runStopDate;
+
bool get isCodeRunning => !(_result?.isFinished ?? true);
String get resultLog => _result?.log ?? '';
+
String get resultOutput => _result?.output ?? '';
+
String get resultLogOutput => resultLog + resultOutput;
bool get isExampleChanged {
@@ -78,14 +96,18 @@ class CodeRunner extends ChangeNotifier {
// Snapshot of additional analytics data at the time when execution started.
Map<String, dynamic> _analyticsData = const {};
+
Map<String, dynamic> get analyticsData => _analyticsData;
bool get canRun => _snippetEditingControllerGetter() != null;
+ static const _attempts = 6;
+ static const _attemptInterval = Duration(seconds: 5);
+ static const _statusCheckInterval = Duration(seconds: 1);
+
void clearResult() {
_eventSnippetContext = null;
_setResult(null);
- notifyListeners();
}
Future<void> reset() async {
@@ -96,21 +118,28 @@ class CodeRunner extends ChangeNotifier {
_runStopDate = null;
_eventSnippetContext = null;
_setResult(null);
- notifyListeners();
}
- void runCode({
- void Function()? onFinish,
+ Future<void> runCode({
Map<String, dynamic> analyticsData = const {},
- }) {
+ }) async {
_analyticsData = analyticsData;
_runStartDate = DateTime.now();
_runStopDate = null;
- notifyListeners();
snippetEditingController = _snippetEditingControllerGetter();
_eventSnippetContext = snippetEditingController!.eventSnippetContext;
- final sdk = snippetEditingController!.sdk;
+ if (!isExampleChanged &&
+ snippetEditingController!.example?.outputs != null) {
+ await _showPrecompiledResult();
+ return;
+ }
+
+ await _runReal();
+ }
+
+ Future<void> _runReal() async {
+ final sdk = snippetEditingController!.sdk;
final parsedPipelineOptions =
parsePipelineOptions(snippetEditingController!.pipelineOptions);
if (parsedPipelineOptions == null) {
@@ -121,37 +150,115 @@ class CodeRunner extends ChangeNotifier {
status: RunCodeStatus.compileError,
),
);
- _runStopDate = DateTime.now();
- notifyListeners();
return;
}
- if (!isExampleChanged &&
- snippetEditingController!.example?.outputs != null) {
- unawaited(_showPrecompiledResult());
- } else {
- final request = RunCodeRequest(
- datasets: snippetEditingController?.example?.datasets ?? [],
- files: snippetEditingController!.getFiles(),
- sdk: snippetEditingController!.sdk,
- pipelineOptions: parsedPipelineOptions,
- );
- _runSubscription = _codeRepository?.runCode(request).listen((event) {
- _setResult(event);
- notifyListeners();
-
- if (event.isFinished) {
- if (onFinish != null) {
- onFinish();
- }
- snippetEditingController = null;
- _runStopDate = DateTime.now();
+ final log = parsedPipelineOptions.isEmpty
+ ? kProcessingStartedText
+ // ignore: prefer_interpolation_to_compose_strings
+ : kProcessingStartedOptionsText +
+ parsedPipelineOptions.entries
+ .map((e) => '--${e.key} ${e.value}')
+ .join(' ') +
+ '\n';
+
+ _setResult(
+ RunCodeResult(
+ log: log,
+ sdk: sdk,
+ status: RunCodeStatus.preparation,
+ ),
+ );
+
+ final request = RunCodeRequest(
+ datasets: snippetEditingController?.example?.datasets ?? [],
+ files: snippetEditingController!.getFiles(),
+ sdk: snippetEditingController!.sdk,
+ pipelineOptions: parsedPipelineOptions,
+ );
+
+ try {
+ final runResponse = await _startExecution(request);
+
+ if (runResponse == null || _result!.isFinished) {
+ // Cancelled while trying to start.
+ final pipelineUuid = runResponse?.pipelineUuid;
+ if (pipelineUuid != null) {
+ await codeClient?.cancelExecution(pipelineUuid);
}
- });
- notifyListeners();
+ return;
+ }
+
+ await Future.delayed(_statusCheckInterval);
+
+ while (!_result!.isFinished) {
+ final statusResponse =
+ await codeClient!.checkStatus(runResponse.pipelineUuid);
+
+ final result = await _getPipelineResult(
+ runResponse.pipelineUuid,
+ statusResponse.status,
+ _result!,
+ );
+
+ _setResultIfNotFinished(result);
+
+ await Future.delayed(_statusCheckInterval);
+ }
+ } on RunCodeError catch (ex) {
+ _setResult(
+ RunCodeResult(
+ errorMessage: ex.message ?? kUnknownErrorText,
+ output: ex.message ?? kUnknownErrorText,
+ sdk: request.sdk,
+ status: RunCodeStatus.unknownError,
+ ),
+ );
+ } on Exception catch (ex) {
+ print(ex); // ignore: avoid_print
+ _setResult(
+ RunCodeResult(
+ errorMessage: kUnknownErrorText,
+ output: kUnknownErrorText,
+ sdk: request.sdk,
+ status: RunCodeStatus.unknownError,
+ ),
+ );
+ } finally {
+ snippetEditingController = null;
}
}
+ Future<RunCodeResponse?> _startExecution(RunCodeRequest request) async {
+ Exception? lastException;
+
+ // Attempts to place the job for execution.
+ // This fails if the backend is overloaded and has not yet scaled up.
+ for (int attemptsLeft = _attempts; --attemptsLeft >= 0;) {
+ if (_result!.isFinished) {
+ return null; // Cancelled while retrying.
+ }
+
+ try {
+ return await codeClient!.runCode(request);
+ } on RunCodeResourceExhaustedError catch (ex) {
+ lastException = ex;
+ }
+
+ // ignore: avoid_print
+ print(
+ 'Got RunCodeResourceExhaustedError, attempts left: $attemptsLeft.',
+ );
+ if (attemptsLeft > 0) {
+ // ignore: avoid_print
+ print('Waiting for $_attemptInterval before retrying.');
+ await Future.delayed(_attemptInterval);
+ }
+ }
+
+ throw lastException ?? Exception('lastException must be filled above.');
+ }
+
/// Resets the error message text so that on the next rebuild
/// of `CodeTextAreaWrapper` it is not picked up and not shown as a toast.
// TODO: Listen to this object outside of widgets,
@@ -195,13 +302,10 @@ class CodeRunner extends ChangeNotifier {
}
snippetEditingController = null;
- // Awaited cancelling subscription here blocks further method execution.
- // TODO: Figure out the reason: https://github.com/apache/beam/issues/25509
- unawaited(_runSubscription?.cancel());
final pipelineUuid = _result?.pipelineUuid ?? '';
if (pipelineUuid.isNotEmpty) {
- await _codeRepository?.cancelExecution(pipelineUuid);
+ await codeClient?.cancelExecution(pipelineUuid);
}
_setResult(
@@ -213,12 +317,9 @@ class CodeRunner extends ChangeNotifier {
'widgets.output.messages.pipelineCancelled'.tr(),
output: _result?.output,
sdk: sdk,
- status: RunCodeStatus.finished,
+ status: RunCodeStatus.cancelled,
),
);
-
- _runStopDate = DateTime.now();
- notifyListeners();
}
Future<void> _showPrecompiledResult() async {
@@ -250,12 +351,21 @@ class CodeRunner extends ChangeNotifier {
status: RunCodeStatus.finished,
),
);
+ }
- _runStopDate = DateTime.now();
- notifyListeners();
+ void _setResultIfNotFinished(RunCodeResult newValue) {
+ if (_result?.isFinished ?? true) {
+ return;
+ }
+ _setResult(newValue);
}
void _setResult(RunCodeResult? newValue) {
+ // ignore: use_if_null_to_convert_nulls_to_bools
+ if (_result?.isFinished == false && newValue?.isFinished == true) {
+ _runStopDate = clock.now();
+ }
+
_result = newValue;
if (newValue == null) {
@@ -270,5 +380,138 @@ class CodeRunner extends ChangeNotifier {
newValue.graph ?? '',
);
}
+
+ notifyListeners();
+ }
+
+ Future<RunCodeResult> _getPipelineResult(
+ String pipelineUuid,
+ RunCodeStatus status,
+ RunCodeResult prevResult,
+ ) async {
+ final prevOutput = prevResult.output ?? '';
+ final prevLog = prevResult.log ?? '';
+ final prevGraph = prevResult.graph ?? '';
+
+ switch (status) {
+ case RunCodeStatus.compileError:
+ final compileOutput = await codeClient!.getCompileOutput(pipelineUuid);
+ return RunCodeResult(
+ graph: prevGraph,
+ log: prevLog,
+ output: compileOutput.output,
+ pipelineUuid: pipelineUuid,
+ sdk: prevResult.sdk,
+ status: status,
+ );
+
+ case RunCodeStatus.timeout:
+ return RunCodeResult(
+ errorMessage: kTimeoutErrorText,
+ graph: prevGraph,
+ log: prevLog,
+ output: kTimeoutErrorText,
+ pipelineUuid: pipelineUuid,
+ sdk: prevResult.sdk,
+ status: status,
+ );
+
+ case RunCodeStatus.runError:
+ final output = await codeClient!.getRunErrorOutput(pipelineUuid);
+ return RunCodeResult(
+ graph: prevGraph,
+ log: prevLog,
+ output: output.output,
+ pipelineUuid: pipelineUuid,
+ sdk: prevResult.sdk,
+ status: status,
+ );
+
+ case RunCodeStatus.validationError:
+ final output = await
codeClient!.getValidationErrorOutput(pipelineUuid);
+ return RunCodeResult(
+ graph: prevGraph,
+ log: prevLog,
+ output: output.output,
+ sdk: prevResult.sdk,
+ status: status,
+ );
+
+ case RunCodeStatus.preparationError:
+ final output =
+ await codeClient!.getPreparationErrorOutput(pipelineUuid);
+ return RunCodeResult(
+ graph: prevGraph,
+ log: prevLog,
+ output: output.output,
+ sdk: prevResult.sdk,
+ status: status,
+ );
+
+ case RunCodeStatus.unknownError:
+ return RunCodeResult(
+ errorMessage: kUnknownErrorText,
+ graph: prevGraph,
+ log: prevLog,
+ output: kUnknownErrorText,
+ pipelineUuid: pipelineUuid,
+ sdk: prevResult.sdk,
+ status: status,
+ );
+
+ case RunCodeStatus.executing:
+ final responses = await Future.wait([
+ codeClient!.getRunOutput(pipelineUuid),
+ codeClient!.getLogOutput(pipelineUuid),
+ prevGraph.isEmpty
+ ? codeClient!.getGraphOutput(pipelineUuid)
+ : Future.value(OutputResponse(output: prevGraph)),
+ ]);
+ final output = responses[0];
+ final log = responses[1];
+ final graph = responses[2];
+ return RunCodeResult(
+ graph: graph.output,
+ log: prevLog + log.output,
+ output: prevOutput + output.output,
+ pipelineUuid: pipelineUuid,
+ sdk: prevResult.sdk,
+ status: status,
+ );
+
+ case RunCodeStatus.cancelled:
+ case RunCodeStatus.finished:
+ final responses = await Future.wait([
+ codeClient!.getRunOutput(pipelineUuid),
+ codeClient!.getLogOutput(pipelineUuid),
+ codeClient!.getRunErrorOutput(pipelineUuid),
+ prevGraph.isEmpty
+ ? codeClient!.getGraphOutput(pipelineUuid)
+ : Future.value(OutputResponse(output: prevGraph)),
+ ]);
+ final output = responses[0];
+ final log = responses[1];
+ final error = responses[2];
+ final graph = responses[3];
+ return RunCodeResult(
+ graph: graph.output,
+ log: prevLog + log.output,
+ output: prevOutput + output.output + error.output,
+ pipelineUuid: pipelineUuid,
+ sdk: prevResult.sdk,
+ status: status,
+ );
+
+ case RunCodeStatus.unspecified:
+ case RunCodeStatus.preparation:
+ case RunCodeStatus.compiling:
+ return RunCodeResult(
+ graph: prevGraph,
+ log: prevLog,
+ pipelineUuid: pipelineUuid,
+ sdk: prevResult.sdk,
+ status: status,
+ );
+ }
}
}
diff --git
a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
index adef2e8ab6c..4b01020c795 100644
---
a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
+++
b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
@@ -36,7 +36,7 @@ import
'../models/example_loading_descriptors/user_shared_example_loading_descri
import '../models/intents.dart';
import '../models/sdk.dart';
import '../models/shortcut.dart';
-import '../repositories/code_repository.dart';
+import '../repositories/code_client/code_client.dart';
import '../services/symbols/loaders/map.dart';
import '../services/symbols/symbols_notifier.dart';
import '../util/logical_keyboard_key.dart';
@@ -67,12 +67,12 @@ class PlaygroundController with ChangeNotifier {
PlaygroundController({
required this.exampleCache,
required this.examplesLoader,
- CodeRepository? codeRepository,
+ CodeClient? codeClient,
}) {
examplesLoader.setPlaygroundController(this);
codeRunner = CodeRunner(
- codeRepository: codeRepository,
+ codeClient: codeClient,
snippetEditingControllerGetter: requireSnippetEditingController,
)..addListener(notifyListeners);
}
diff --git
a/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart
b/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart
index 04f4fb29e2f..4392cda9fac 100644
---
a/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart
+++
b/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart
@@ -23,6 +23,7 @@ import '../../api/iis_workaround_channel.dart';
import '../../api/v1/api.pbgrpc.dart' as grpc;
import '../../models/sdk.dart';
import '../../util/pipeline_options.dart';
+import '../../util/run_with_retry.dart';
import '../dataset_grpc_extension.dart';
import '../models/check_status_response.dart';
import '../models/output_response.dart';
@@ -92,6 +93,14 @@ class GrpcCodeClient implements CodeClient {
@override
Future<CheckStatusResponse> checkStatus(
String pipelineUuid,
+ ) async {
+ return runWithRetry(
+ () => _checkStatusWithRetry(pipelineUuid),
+ );
+ }
+
+ Future<CheckStatusResponse> _checkStatusWithRetry(
+ String pipelineUuid,
) async {
final response = await _runSafely(
() => _defaultClient.checkStatus(
@@ -204,10 +213,12 @@ class GrpcCodeClient implements CodeClient {
try {
return await invoke();
} on GrpcError catch (error) {
- // Internet unavailable issue also returns unknown code error,
- // so message was overwritten.
- if (error.code == StatusCode.unknown) {
- throw RunCodeError(message: 'errors.unknownError'.tr());
+ switch (error.code) {
+ case StatusCode.unknown:
+ // The default can be misleading for this.
+ throw RunCodeError(message: 'errors.unknownError'.tr());
+ case StatusCode.resourceExhausted:
+ throw RunCodeResourceExhaustedError(message: error.message);
}
throw RunCodeError(message: error.message);
} on Exception catch (_) {
@@ -250,6 +261,7 @@ class GrpcCodeClient implements CodeClient {
case grpc.Status.STATUS_EXECUTING:
return RunCodeStatus.executing;
case grpc.Status.STATUS_CANCELED:
+ return RunCodeStatus.cancelled;
case grpc.Status.STATUS_FINISHED:
return RunCodeStatus.finished;
case grpc.Status.STATUS_COMPILE_ERROR:
diff --git
a/playground/frontend/playground_components/lib/src/repositories/code_repository.dart
b/playground/frontend/playground_components/lib/src/repositories/code_repository.dart
deleted file mode 100644
index bd6a5206ed1..00000000000
---
a/playground/frontend/playground_components/lib/src/repositories/code_repository.dart
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../util/run_with_retry.dart';
-import 'code_client/code_client.dart';
-import 'models/output_response.dart';
-import 'models/run_code_error.dart';
-import 'models/run_code_request.dart';
-import 'models/run_code_result.dart';
-
-const kPipelineCheckDelay = Duration(seconds: 1);
-const kTimeoutErrorText =
- 'Pipeline exceeded Playground execution timeout and was terminated. '
- 'We recommend installing Apache Beam '
- 'https://beam.apache.org/get-started/downloads/ '
- 'to try examples without timeout limitation.';
-const kUnknownErrorText =
- 'Something went wrong. Please try again later or create a GitHub issue';
-const kProcessingStartedText = 'The processing has been started\n';
-const kProcessingStartedOptionsText =
- 'The processing has been started with the pipeline options: ';
-
-// TODO(alexeyinkin): Rename. This is not a repository but a higher level
client.
-class CodeRepository {
- final CodeClient _client;
-
- CodeRepository({
- required CodeClient client,
- }) : _client = client;
-
- Stream<RunCodeResult> runCode(RunCodeRequest request) async* {
- try {
- final log = request.pipelineOptions.isEmpty
- ? kProcessingStartedText
- // ignore: prefer_interpolation_to_compose_strings
- : kProcessingStartedOptionsText +
- request.pipelineOptions.entries
- .map((e) => '--${e.key} ${e.value}')
- .join(' ') + '\n';
- final initResult = RunCodeResult(
- log: log,
- sdk: request.sdk,
- status: RunCodeStatus.preparation,
- );
- yield initResult;
-
- final runCodeResponse = await _client.runCode(request);
- final pipelineUuid = runCodeResponse.pipelineUuid;
-
- yield* _checkPipelineExecution(
- pipelineUuid,
- prevResult: initResult,
- );
- } on RunCodeError catch (error) {
- yield RunCodeResult(
- errorMessage: error.message ?? kUnknownErrorText,
- output: error.message ?? kUnknownErrorText,
- sdk: request.sdk,
- status: RunCodeStatus.unknownError,
- );
- }
- }
-
- Future<void> cancelExecution(String pipelineUuid) {
- return _client.cancelExecution(pipelineUuid);
- }
-
- Stream<RunCodeResult> _checkPipelineExecution(
- String pipelineUuid, {
- required RunCodeResult prevResult,
- }) async* {
- try {
- final statusResponse = await runWithRetry(
- () => _client.checkStatus(pipelineUuid),
- );
- final result = await _getPipelineResult(
- pipelineUuid,
- statusResponse.status,
- prevResult,
- );
- yield result;
- if (!result.isFinished) {
- await Future.delayed(kPipelineCheckDelay);
- yield* _checkPipelineExecution(
- pipelineUuid,
- prevResult: result,
- );
- }
- } on RunCodeError catch (error) {
- yield RunCodeResult(
- errorMessage: error.message ?? kUnknownErrorText,
- output: error.message ?? kUnknownErrorText,
- pipelineUuid: prevResult.pipelineUuid,
- sdk: prevResult.sdk,
- status: RunCodeStatus.unknownError,
- );
- }
- }
-
- Future<RunCodeResult> _getPipelineResult(
- String pipelineUuid,
- RunCodeStatus status,
- RunCodeResult prevResult,
- ) async {
- final prevOutput = prevResult.output ?? '';
- final prevLog = prevResult.log ?? '';
- final prevGraph = prevResult.graph ?? '';
-
- switch (status) {
- case RunCodeStatus.compileError:
- final compileOutput = await _client.getCompileOutput(pipelineUuid);
- return RunCodeResult(
- graph: prevGraph,
- log: prevLog,
- output: compileOutput.output,
- pipelineUuid: pipelineUuid,
- sdk: prevResult.sdk,
- status: status,
- );
-
- case RunCodeStatus.timeout:
- return RunCodeResult(
- errorMessage: kTimeoutErrorText,
- graph: prevGraph,
- log: prevLog,
- output: kTimeoutErrorText,
- pipelineUuid: pipelineUuid,
- sdk: prevResult.sdk,
- status: status,
- );
-
- case RunCodeStatus.runError:
- final output = await _client.getRunErrorOutput(pipelineUuid);
- return RunCodeResult(
- graph: prevGraph,
- log: prevLog,
- output: output.output,
- pipelineUuid: pipelineUuid,
- sdk: prevResult.sdk,
- status: status,
- );
-
- case RunCodeStatus.validationError:
- final output = await _client.getValidationErrorOutput(pipelineUuid);
- return RunCodeResult(
- graph: prevGraph,
- log: prevLog,
- output: output.output,
- sdk: prevResult.sdk,
- status: status,
- );
-
- case RunCodeStatus.preparationError:
- final output = await _client.getPreparationErrorOutput(pipelineUuid);
- return RunCodeResult(
- graph: prevGraph,
- log: prevLog,
- output: output.output,
- sdk: prevResult.sdk,
- status: status,
- );
-
- case RunCodeStatus.unknownError:
- return RunCodeResult(
- errorMessage: kUnknownErrorText,
- graph: prevGraph,
- log: prevLog,
- output: kUnknownErrorText,
- pipelineUuid: pipelineUuid,
- sdk: prevResult.sdk,
- status: status,
- );
-
- case RunCodeStatus.executing:
- final responses = await Future.wait([
- _client.getRunOutput(pipelineUuid),
- _client.getLogOutput(pipelineUuid),
- prevGraph.isEmpty
- ? _client.getGraphOutput(pipelineUuid)
- : Future.value(OutputResponse(output: prevGraph)),
- ]);
- final output = responses[0];
- final log = responses[1];
- final graph = responses[2];
- return RunCodeResult(
- graph: graph.output,
- log: prevLog + log.output,
- output: prevOutput + output.output,
- pipelineUuid: pipelineUuid,
- sdk: prevResult.sdk,
- status: status,
- );
-
- case RunCodeStatus.finished:
- final responses = await Future.wait([
- _client.getRunOutput(pipelineUuid),
- _client.getLogOutput(pipelineUuid),
- _client.getRunErrorOutput(pipelineUuid),
- prevGraph.isEmpty
- ? _client.getGraphOutput(pipelineUuid)
- : Future.value(OutputResponse(output: prevGraph)),
- ]);
- final output = responses[0];
- final log = responses[1];
- final error = responses[2];
- final graph = responses[3];
- return RunCodeResult(
- graph: graph.output,
- log: prevLog + log.output,
- output: prevOutput + output.output + error.output,
- pipelineUuid: pipelineUuid,
- sdk: prevResult.sdk,
- status: status,
- );
-
- default:
- return RunCodeResult(
- graph: prevGraph,
- log: prevLog,
- pipelineUuid: pipelineUuid,
- sdk: prevResult.sdk,
- status: status,
- );
- }
- }
-}
diff --git
a/playground/frontend/playground_components/lib/src/repositories/models/run_code_error.dart
b/playground/frontend/playground_components/lib/src/repositories/models/run_code_error.dart
index 68379a2473b..6b5eb340136 100644
---
a/playground/frontend/playground_components/lib/src/repositories/models/run_code_error.dart
+++
b/playground/frontend/playground_components/lib/src/repositories/models/run_code_error.dart
@@ -23,3 +23,9 @@ class RunCodeError implements Exception {
this.message,
});
}
+
+class RunCodeResourceExhaustedError extends RunCodeError {
+ const RunCodeResourceExhaustedError({
+ required super.message,
+ });
+}
diff --git
a/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart
b/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart
index 506fe54f633..7ca6e104f1d 100644
---
a/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart
+++
b/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart
@@ -21,28 +21,30 @@ import 'package:equatable/equatable.dart';
import '../../models/sdk.dart';
enum RunCodeStatus {
- unspecified,
- preparation,
- preparationError,
- validationError,
- compiling,
+ cancelled,
compileError,
+ compiling,
executing,
- runError,
finished,
+ preparation,
+ preparationError,
+ runError,
timeout,
unknownError,
+ unspecified,
+ validationError,
}
-const kFinishedStatuses = [
- RunCodeStatus.unknownError,
- RunCodeStatus.timeout,
+const kFinishedStatuses = {
+ RunCodeStatus.cancelled,
RunCodeStatus.compileError,
+ RunCodeStatus.finished,
+ RunCodeStatus.preparationError,
RunCodeStatus.runError,
+ RunCodeStatus.timeout,
+ RunCodeStatus.unknownError,
RunCodeStatus.validationError,
- RunCodeStatus.preparationError,
- RunCodeStatus.finished,
-];
+};
class RunCodeResult with EquatableMixin {
final String? errorMessage;
diff --git
a/playground/frontend/playground_components/lib/src/widgets/run_or_cancel_button.dart
b/playground/frontend/playground_components/lib/src/widgets/run_or_cancel_button.dart
index 444a33e7436..79c00dabdf5 100644
---
a/playground/frontend/playground_components/lib/src/widgets/run_or_cancel_button.dart
+++
b/playground/frontend/playground_components/lib/src/widgets/run_or_cancel_button.dart
@@ -24,6 +24,7 @@ import '../controllers/playground_controller.dart';
import '../models/toast.dart';
import '../models/toast_type.dart';
import '../playground_components.dart';
+import '../repositories/models/run_code_result.dart';
import 'run_button.dart';
class RunOrCancelButton extends StatelessWidget {
@@ -49,11 +50,13 @@ class RunOrCancelButton extends StatelessWidget {
(_) => PlaygroundComponents.toastNotifier.add(_getErrorToast()),
);
},
- runCode: () {
+ runCode: () async {
beforeRun?.call();
- playgroundController.codeRunner.runCode(
- onFinish: () => onComplete?.call(playgroundController.codeRunner),
- );
+ final runner = playgroundController.codeRunner;
+ await runner.runCode();
+ if (runner.result?.status == RunCodeStatus.finished) {
+ onComplete?.call(playgroundController.codeRunner);
+ }
},
);
}
diff --git
a/playground/frontend/playground_components/test/src/controllers/code_runner_test.dart
b/playground/frontend/playground_components/test/src/controllers/code_runner_test.dart
new file mode 100644
index 00000000000..ff3766c43f3
--- /dev/null
+++
b/playground/frontend/playground_components/test/src/controllers/code_runner_test.dart
@@ -0,0 +1,258 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:playground_components/src/cache/example_cache.dart';
+import 'package:playground_components/src/controllers/code_runner.dart';
+import
'package:playground_components/src/controllers/example_loaders/examples_loader.dart';
+import
'package:playground_components/src/controllers/snippet_editing_controller.dart';
+import 'package:playground_components/src/models/sdk.dart';
+import
'package:playground_components/src/repositories/code_client/code_client.dart';
+import
'package:playground_components/src/repositories/models/check_status_response.dart';
+import
'package:playground_components/src/repositories/models/output_response.dart';
+import
'package:playground_components/src/repositories/models/run_code_response.dart';
+import
'package:playground_components/src/repositories/models/run_code_result.dart';
+
+import 'code_runner_test.mocks.dart';
+
+const _sdk = Sdk.java;
+
+const kPipelineUuid = '1234';
+const kRunOutput = 'RunOutput';
+const kLogOutput = 'LogOutput';
+const kCompileOutput = 'CompileOutput';
+const kGraphOutput = 'GraphOutput';
+const kRunErrorOutput = 'RunErrorOutput';
+const kPreparationErrorOutput = 'PreparationErrorOutput';
+const kValidationErrorOutput = 'ValidationErrorOutput';
+
+const kRunCodeResponse = RunCodeResponse(pipelineUuid: kPipelineUuid);
+const kFinishedStatusResponse = CheckStatusResponse(
+ status: RunCodeStatus.finished,
+);
+const kRunErrorStatusResponse = CheckStatusResponse(
+ status: RunCodeStatus.runError,
+);
+const kCompileErrorStatusResponse = CheckStatusResponse(
+ status: RunCodeStatus.compileError,
+);
+const kValidationErrorStatusResponse = CheckStatusResponse(
+ status: RunCodeStatus.validationError,
+);
+const kPreparationErrorStatusResponse = CheckStatusResponse(
+ status: RunCodeStatus.preparationError,
+);
+
+const kRunOutputResponse = OutputResponse(output: kRunOutput);
+const kLogOutputResponse = OutputResponse(output: kLogOutput);
+const kCompileOutputResponse = OutputResponse(output: kCompileOutput);
+const kRunErrorOutputResponse = OutputResponse(output: kRunErrorOutput);
+const kGraphResponse = OutputResponse(output: kGraphOutput);
+
+const kValidationErrorOutputResponse =
+ OutputResponse(output: kValidationErrorOutput);
+
+const kPreparationErrorOutputResponse =
+ OutputResponse(output: kPreparationErrorOutput);
+
+@GenerateMocks([CodeClient, ExamplesLoader, ExampleCache])
+void main() {
+ var results = <RunCodeResult?>[];
+ var client = MockCodeClient();
+ var runner = CodeRunner(
+ snippetEditingControllerGetter: () => SnippetEditingController(sdk: _sdk),
+ );
+
+ setUp(() async {
+ results = [];
+ client = MockCodeClient();
+ runner = CodeRunner(
+ snippetEditingControllerGetter: () => SnippetEditingController(sdk:
_sdk),
+ codeClient: client,
+ );
+
+ runner.addListener(() {
+ results.add(runner.result);
+ });
+
+ when(client.runCode(any)).thenAnswer(
+ (_) async => kRunCodeResponse,
+ );
+ when(client.getCompileOutput(kPipelineUuid)).thenAnswer(
+ (_) async => kCompileOutputResponse,
+ );
+ when(client.getRunOutput(kPipelineUuid)).thenAnswer(
+ (_) async => kRunOutputResponse,
+ );
+ when(client.getRunErrorOutput(kPipelineUuid)).thenAnswer(
+ (_) async => kRunErrorOutputResponse,
+ );
+ when(client.getLogOutput(kPipelineUuid)).thenAnswer(
+ (_) async => kLogOutputResponse,
+ );
+ when(client.getGraphOutput(kPipelineUuid)).thenAnswer(
+ (_) async => kGraphResponse,
+ );
+ when(client.getValidationErrorOutput(kPipelineUuid)).thenAnswer(
+ (_) async => kValidationErrorOutputResponse,
+ );
+ when(client.getPreparationErrorOutput(kPipelineUuid)).thenAnswer(
+ (_) async => kPreparationErrorOutputResponse,
+ );
+ });
+
+ group('CodeRunner.runCode', () {
+ test('finished', () async {
+ when(client.checkStatus(kPipelineUuid)).thenAnswer(
+ (_) async => kFinishedStatusResponse,
+ );
+
+ await runner.runCode();
+
+ expect(
+ results,
+ const [
+ RunCodeResult(
+ log: kProcessingStartedText,
+ sdk: _sdk,
+ status: RunCodeStatus.preparation,
+ ),
+ RunCodeResult(
+ pipelineUuid: kPipelineUuid,
+ output: kRunOutput + kRunErrorOutput,
+ log: kProcessingStartedText + kLogOutput,
+ graph: kGraphOutput,
+ sdk: _sdk,
+ status: RunCodeStatus.finished,
+ ),
+ ],
+ );
+
+ // compile output should not be called
+ verifyNever(client.getCompileOutput(kPipelineUuid));
+ });
+
+ test('compileError', () async {
+ when(client.checkStatus(kPipelineUuid)).thenAnswer(
+ (_) async => kCompileErrorStatusResponse,
+ );
+
+ await runner.runCode();
+
+ expect(
+ results,
+ const [
+ RunCodeResult(
+ log: kProcessingStartedText,
+ sdk: _sdk,
+ status: RunCodeStatus.preparation,
+ ),
+ RunCodeResult(
+ graph: '',
+ log: kProcessingStartedText,
+ output: kCompileOutput,
+ pipelineUuid: kPipelineUuid,
+ sdk: _sdk,
+ status: RunCodeStatus.compileError,
+ ),
+ ],
+ );
+ });
+
+ test('validationError', () async {
+ when(client.checkStatus(kPipelineUuid)).thenAnswer(
+ (_) async => kValidationErrorStatusResponse,
+ );
+
+ await runner.runCode();
+
+ expect(
+ results,
+ const [
+ RunCodeResult(
+ log: kProcessingStartedText,
+ sdk: _sdk,
+ status: RunCodeStatus.preparation,
+ ),
+ RunCodeResult(
+ graph: '',
+ log: kProcessingStartedText,
+ output: kValidationErrorOutput,
+ sdk: _sdk,
+ status: RunCodeStatus.validationError,
+ ),
+ ],
+ );
+ });
+
+ test('preparationError', () async {
+ when(client.checkStatus(kPipelineUuid)).thenAnswer(
+ (_) async => kPreparationErrorStatusResponse,
+ );
+
+ await runner.runCode();
+
+ expect(
+ results,
+ const [
+ RunCodeResult(
+ log: kProcessingStartedText,
+ sdk: _sdk,
+ status: RunCodeStatus.preparation,
+ ),
+ RunCodeResult(
+ graph: '',
+ log: kProcessingStartedText,
+ output: kPreparationErrorOutput,
+ sdk: _sdk,
+ status: RunCodeStatus.preparationError,
+ ),
+ ],
+ );
+ });
+
+ test('runError', () async {
+ when(client.checkStatus(kPipelineUuid)).thenAnswer(
+ (_) async => kRunErrorStatusResponse,
+ );
+
+ await runner.runCode();
+
+ expect(
+ results,
+ const [
+ RunCodeResult(
+ log: kProcessingStartedText,
+ sdk: _sdk,
+ status: RunCodeStatus.preparation,
+ ),
+ RunCodeResult(
+ graph: '',
+ log: kProcessingStartedText,
+ output: kRunErrorOutput,
+ pipelineUuid: kPipelineUuid,
+ sdk: _sdk,
+ status: RunCodeStatus.runError,
+ ),
+ ],
+ );
+ });
+ });
+}
diff --git
a/playground/frontend/playground_components/test/src/controllers/code_runner_test.mocks.dart
b/playground/frontend/playground_components/test/src/controllers/code_runner_test.mocks.dart
new file mode 100644
index 00000000000..892ad2d46df
--- /dev/null
+++
b/playground/frontend/playground_components/test/src/controllers/code_runner_test.mocks.dart
@@ -0,0 +1,564 @@
+// Mocks generated by Mockito 5.3.2 from annotations
+// in playground_components/test/src/controllers/code_runner_test.dart.
+// Do not manually edit this file.
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'dart:async' as _i9;
+import 'dart:ui' as _i21;
+
+import 'package:mockito/mockito.dart' as _i1;
+import 'package:playground_components/src/api/v1/api.pb.dart' as _i2;
+import 'package:playground_components/src/cache/example_cache.dart' as _i15;
+import
'package:playground_components/src/controllers/example_loaders/example_loader_factory.dart'
+ as _i6;
+import
'package:playground_components/src/controllers/example_loaders/examples_loader.dart'
+ as _i12;
+import
'package:playground_components/src/controllers/playground_controller.dart'
+ as _i13;
+import 'package:playground_components/src/models/category_with_examples.dart'
+ as _i16;
+import 'package:playground_components/src/models/example.dart' as _i7;
+import 'package:playground_components/src/models/example_base.dart' as _i20;
+import
'package:playground_components/src/models/example_loading_descriptors/examples_loading_descriptor.dart'
+ as _i14;
+import 'package:playground_components/src/models/example_view_options.dart'
+ as _i18;
+import 'package:playground_components/src/models/loading_status.dart' as _i17;
+import 'package:playground_components/src/models/sdk.dart' as _i10;
+import 'package:playground_components/src/models/snippet_file.dart' as _i19;
+import
'package:playground_components/src/repositories/code_client/code_client.dart'
+ as _i8;
+import
'package:playground_components/src/repositories/models/check_status_response.dart'
+ as _i4;
+import
'package:playground_components/src/repositories/models/output_response.dart'
+ as _i5;
+import
'package:playground_components/src/repositories/models/run_code_request.dart'
+ as _i11;
+import
'package:playground_components/src/repositories/models/run_code_response.dart'
+ as _i3;
+
+// ignore_for_file: type=lint
+// ignore_for_file: avoid_redundant_argument_values
+// ignore_for_file: avoid_setters_without_getters
+// ignore_for_file: comment_references
+// ignore_for_file: implementation_imports
+// ignore_for_file: invalid_use_of_visible_for_testing_member
+// ignore_for_file: prefer_const_constructors
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: camel_case_types
+// ignore_for_file: subtype_of_sealed_class
+
+class _FakeGetMetadataResponse_0 extends _i1.SmartFake
+ implements _i2.GetMetadataResponse {
+ _FakeGetMetadataResponse_0(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeRunCodeResponse_1 extends _i1.SmartFake
+ implements _i3.RunCodeResponse {
+ _FakeRunCodeResponse_1(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeCheckStatusResponse_2 extends _i1.SmartFake
+ implements _i4.CheckStatusResponse {
+ _FakeCheckStatusResponse_2(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeOutputResponse_3 extends _i1.SmartFake
+ implements _i5.OutputResponse {
+ _FakeOutputResponse_3(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeExampleLoaderFactory_4 extends _i1.SmartFake
+ implements _i6.ExampleLoaderFactory {
+ _FakeExampleLoaderFactory_4(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeExample_5 extends _i1.SmartFake implements _i7.Example {
+ _FakeExample_5(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+/// A class which mocks [CodeClient].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockCodeClient extends _i1.Mock implements _i8.CodeClient {
+ MockCodeClient() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ _i9.Future<_i2.GetMetadataResponse> getMetadata(_i10.Sdk? sdk) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getMetadata,
+ [sdk],
+ ),
+ returnValue: _i9.Future<_i2.GetMetadataResponse>.value(
+ _FakeGetMetadataResponse_0(
+ this,
+ Invocation.method(
+ #getMetadata,
+ [sdk],
+ ),
+ )),
+ ) as _i9.Future<_i2.GetMetadataResponse>);
+ @override
+ _i9.Future<_i3.RunCodeResponse> runCode(_i11.RunCodeRequest? request) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #runCode,
+ [request],
+ ),
+ returnValue:
+ _i9.Future<_i3.RunCodeResponse>.value(_FakeRunCodeResponse_1(
+ this,
+ Invocation.method(
+ #runCode,
+ [request],
+ ),
+ )),
+ ) as _i9.Future<_i3.RunCodeResponse>);
+ @override
+ _i9.Future<void> cancelExecution(String? pipelineUuid) =>
(super.noSuchMethod(
+ Invocation.method(
+ #cancelExecution,
+ [pipelineUuid],
+ ),
+ returnValue: _i9.Future<void>.value(),
+ returnValueForMissingStub: _i9.Future<void>.value(),
+ ) as _i9.Future<void>);
+ @override
+ _i9.Future<_i4.CheckStatusResponse> checkStatus(String? pipelineUuid) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #checkStatus,
+ [pipelineUuid],
+ ),
+ returnValue: _i9.Future<_i4.CheckStatusResponse>.value(
+ _FakeCheckStatusResponse_2(
+ this,
+ Invocation.method(
+ #checkStatus,
+ [pipelineUuid],
+ ),
+ )),
+ ) as _i9.Future<_i4.CheckStatusResponse>);
+ @override
+ _i9.Future<_i5.OutputResponse> getCompileOutput(String? pipelineUuid) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getCompileOutput,
+ [pipelineUuid],
+ ),
+ returnValue:
_i9.Future<_i5.OutputResponse>.value(_FakeOutputResponse_3(
+ this,
+ Invocation.method(
+ #getCompileOutput,
+ [pipelineUuid],
+ ),
+ )),
+ ) as _i9.Future<_i5.OutputResponse>);
+ @override
+ _i9.Future<_i5.OutputResponse> getRunOutput(String? pipelineUuid) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getRunOutput,
+ [pipelineUuid],
+ ),
+ returnValue:
_i9.Future<_i5.OutputResponse>.value(_FakeOutputResponse_3(
+ this,
+ Invocation.method(
+ #getRunOutput,
+ [pipelineUuid],
+ ),
+ )),
+ ) as _i9.Future<_i5.OutputResponse>);
+ @override
+ _i9.Future<_i5.OutputResponse> getLogOutput(String? pipelineUuid) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getLogOutput,
+ [pipelineUuid],
+ ),
+ returnValue:
_i9.Future<_i5.OutputResponse>.value(_FakeOutputResponse_3(
+ this,
+ Invocation.method(
+ #getLogOutput,
+ [pipelineUuid],
+ ),
+ )),
+ ) as _i9.Future<_i5.OutputResponse>);
+ @override
+ _i9.Future<_i5.OutputResponse> getRunErrorOutput(String? pipelineUuid) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getRunErrorOutput,
+ [pipelineUuid],
+ ),
+ returnValue:
_i9.Future<_i5.OutputResponse>.value(_FakeOutputResponse_3(
+ this,
+ Invocation.method(
+ #getRunErrorOutput,
+ [pipelineUuid],
+ ),
+ )),
+ ) as _i9.Future<_i5.OutputResponse>);
+ @override
+ _i9.Future<_i5.OutputResponse> getValidationErrorOutput(
+ String? pipelineUuid) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getValidationErrorOutput,
+ [pipelineUuid],
+ ),
+ returnValue:
_i9.Future<_i5.OutputResponse>.value(_FakeOutputResponse_3(
+ this,
+ Invocation.method(
+ #getValidationErrorOutput,
+ [pipelineUuid],
+ ),
+ )),
+ ) as _i9.Future<_i5.OutputResponse>);
+ @override
+ _i9.Future<_i5.OutputResponse> getPreparationErrorOutput(
+ String? pipelineUuid) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getPreparationErrorOutput,
+ [pipelineUuid],
+ ),
+ returnValue:
_i9.Future<_i5.OutputResponse>.value(_FakeOutputResponse_3(
+ this,
+ Invocation.method(
+ #getPreparationErrorOutput,
+ [pipelineUuid],
+ ),
+ )),
+ ) as _i9.Future<_i5.OutputResponse>);
+ @override
+ _i9.Future<_i5.OutputResponse> getGraphOutput(String? pipelineUuid) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getGraphOutput,
+ [pipelineUuid],
+ ),
+ returnValue:
_i9.Future<_i5.OutputResponse>.value(_FakeOutputResponse_3(
+ this,
+ Invocation.method(
+ #getGraphOutput,
+ [pipelineUuid],
+ ),
+ )),
+ ) as _i9.Future<_i5.OutputResponse>);
+}
+
+/// A class which mocks [ExamplesLoader].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockExamplesLoader extends _i1.Mock implements _i12.ExamplesLoader {
+ MockExamplesLoader() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ _i6.ExampleLoaderFactory get defaultFactory => (super.noSuchMethod(
+ Invocation.getter(#defaultFactory),
+ returnValue: _FakeExampleLoaderFactory_4(
+ this,
+ Invocation.getter(#defaultFactory),
+ ),
+ ) as _i6.ExampleLoaderFactory);
+ @override
+ void setPlaygroundController(_i13.PlaygroundController? value) =>
+ super.noSuchMethod(
+ Invocation.method(
+ #setPlaygroundController,
+ [value],
+ ),
+ returnValueForMissingStub: null,
+ );
+ @override
+ _i9.Future<void> loadIfNew(_i14.ExamplesLoadingDescriptor? descriptor) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #loadIfNew,
+ [descriptor],
+ ),
+ returnValue: _i9.Future<void>.value(),
+ returnValueForMissingStub: _i9.Future<void>.value(),
+ ) as _i9.Future<void>);
+ @override
+ _i9.Future<void> load(_i14.ExamplesLoadingDescriptor? descriptor) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #load,
+ [descriptor],
+ ),
+ returnValue: _i9.Future<void>.value(),
+ returnValueForMissingStub: _i9.Future<void>.value(),
+ ) as _i9.Future<void>);
+ @override
+ _i9.Future<void> loadDefaultIfAny(_i10.Sdk? sdk) => (super.noSuchMethod(
+ Invocation.method(
+ #loadDefaultIfAny,
+ [sdk],
+ ),
+ returnValue: _i9.Future<void>.value(),
+ returnValueForMissingStub: _i9.Future<void>.value(),
+ ) as _i9.Future<void>);
+}
+
+/// A class which mocks [ExampleCache].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockExampleCache extends _i1.Mock implements _i15.ExampleCache {
+ MockExampleCache() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ Map<_i10.Sdk, List<_i16.CategoryWithExamples>> get categoryListsBySdk =>
+ (super.noSuchMethod(
+ Invocation.getter(#categoryListsBySdk),
+ returnValue: <_i10.Sdk, List<_i16.CategoryWithExamples>>{},
+ ) as Map<_i10.Sdk, List<_i16.CategoryWithExamples>>);
+ @override
+ Map<_i10.Sdk, _i7.Example> get defaultExamplesBySdk => (super.noSuchMethod(
+ Invocation.getter(#defaultExamplesBySdk),
+ returnValue: <_i10.Sdk, _i7.Example>{},
+ ) as Map<_i10.Sdk, _i7.Example>);
+ @override
+ bool get isSelectorOpened => (super.noSuchMethod(
+ Invocation.getter(#isSelectorOpened),
+ returnValue: false,
+ ) as bool);
+ @override
+ set isSelectorOpened(bool? _isSelectorOpened) => super.noSuchMethod(
+ Invocation.setter(
+ #isSelectorOpened,
+ _isSelectorOpened,
+ ),
+ returnValueForMissingStub: null,
+ );
+ @override
+ _i9.Future<void> get allExamplesFuture => (super.noSuchMethod(
+ Invocation.getter(#allExamplesFuture),
+ returnValue: _i9.Future<void>.value(),
+ ) as _i9.Future<void>);
+ @override
+ _i17.LoadingStatus get catalogStatus => (super.noSuchMethod(
+ Invocation.getter(#catalogStatus),
+ returnValue: _i17.LoadingStatus.loading,
+ ) as _i17.LoadingStatus);
+ @override
+ bool get hasListeners => (super.noSuchMethod(
+ Invocation.getter(#hasListeners),
+ returnValue: false,
+ ) as bool);
+ @override
+ _i9.Future<void> loadAllPrecompiledObjectsIfNot() => (super.noSuchMethod(
+ Invocation.method(
+ #loadAllPrecompiledObjectsIfNot,
+ [],
+ ),
+ returnValue: _i9.Future<void>.value(),
+ returnValueForMissingStub: _i9.Future<void>.value(),
+ ) as _i9.Future<void>);
+ @override
+ List<_i16.CategoryWithExamples> getCategories(_i10.Sdk? sdk) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getCategories,
+ [sdk],
+ ),
+ returnValue: <_i16.CategoryWithExamples>[],
+ ) as List<_i16.CategoryWithExamples>);
+ @override
+ _i9.Future<_i7.Example> getPrecompiledObject(
+ String? path,
+ _i10.Sdk? sdk,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getPrecompiledObject,
+ [
+ path,
+ sdk,
+ ],
+ ),
+ returnValue: _i9.Future<_i7.Example>.value(_FakeExample_5(
+ this,
+ Invocation.method(
+ #getPrecompiledObject,
+ [
+ path,
+ sdk,
+ ],
+ ),
+ )),
+ ) as _i9.Future<_i7.Example>);
+ @override
+ _i9.Future<_i7.Example> loadSharedExample(
+ String? id, {
+ required _i18.ExampleViewOptions? viewOptions,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #loadSharedExample,
+ [id],
+ {#viewOptions: viewOptions},
+ ),
+ returnValue: _i9.Future<_i7.Example>.value(_FakeExample_5(
+ this,
+ Invocation.method(
+ #loadSharedExample,
+ [id],
+ {#viewOptions: viewOptions},
+ ),
+ )),
+ ) as _i9.Future<_i7.Example>);
+ @override
+ _i9.Future<String> saveSnippet({
+ required List<_i19.SnippetFile>? files,
+ required _i10.Sdk? sdk,
+ required String? pipelineOptions,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #saveSnippet,
+ [],
+ {
+ #files: files,
+ #sdk: sdk,
+ #pipelineOptions: pipelineOptions,
+ },
+ ),
+ returnValue: _i9.Future<String>.value(''),
+ ) as _i9.Future<String>);
+ @override
+ _i9.Future<_i7.Example> loadExampleInfo(_i20.ExampleBase? example) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #loadExampleInfo,
+ [example],
+ ),
+ returnValue: _i9.Future<_i7.Example>.value(_FakeExample_5(
+ this,
+ Invocation.method(
+ #loadExampleInfo,
+ [example],
+ ),
+ )),
+ ) as _i9.Future<_i7.Example>);
+ @override
+ void setSelectorOpened(bool? value) => super.noSuchMethod(
+ Invocation.method(
+ #setSelectorOpened,
+ [value],
+ ),
+ returnValueForMissingStub: null,
+ );
+ @override
+ _i9.Future<_i7.Example?> getDefaultExampleBySdk(_i10.Sdk? sdk) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getDefaultExampleBySdk,
+ [sdk],
+ ),
+ returnValue: _i9.Future<_i7.Example?>.value(),
+ ) as _i9.Future<_i7.Example?>);
+ @override
+ _i9.Future<void> loadDefaultPrecompiledObjects() => (super.noSuchMethod(
+ Invocation.method(
+ #loadDefaultPrecompiledObjects,
+ [],
+ ),
+ returnValue: _i9.Future<void>.value(),
+ returnValueForMissingStub: _i9.Future<void>.value(),
+ ) as _i9.Future<void>);
+ @override
+ _i9.Future<void> loadDefaultPrecompiledObjectsIfNot() => (super.noSuchMethod(
+ Invocation.method(
+ #loadDefaultPrecompiledObjectsIfNot,
+ [],
+ ),
+ returnValue: _i9.Future<void>.value(),
+ returnValueForMissingStub: _i9.Future<void>.value(),
+ ) as _i9.Future<void>);
+ @override
+ _i9.Future<_i20.ExampleBase?> getCatalogExampleByPath(String? path) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getCatalogExampleByPath,
+ [path],
+ ),
+ returnValue: _i9.Future<_i20.ExampleBase?>.value(),
+ ) as _i9.Future<_i20.ExampleBase?>);
+ @override
+ void addListener(_i21.VoidCallback? listener) => super.noSuchMethod(
+ Invocation.method(
+ #addListener,
+ [listener],
+ ),
+ returnValueForMissingStub: null,
+ );
+ @override
+ void removeListener(_i21.VoidCallback? listener) => super.noSuchMethod(
+ Invocation.method(
+ #removeListener,
+ [listener],
+ ),
+ returnValueForMissingStub: null,
+ );
+ @override
+ void dispose() => super.noSuchMethod(
+ Invocation.method(
+ #dispose,
+ [],
+ ),
+ returnValueForMissingStub: null,
+ );
+ @override
+ void notifyListeners() => super.noSuchMethod(
+ Invocation.method(
+ #notifyListeners,
+ [],
+ ),
+ returnValueForMissingStub: null,
+ );
+}
diff --git
a/playground/frontend/playground_components/test/src/repositories/code_repository_test.dart
b/playground/frontend/playground_components/test/src/repositories/code_repository_test.dart
deleted file mode 100644
index 2b07ea9d2eb..00000000000
---
a/playground/frontend/playground_components/test/src/repositories/code_repository_test.dart
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import 'package:flutter_test/flutter_test.dart';
-import 'package:mockito/annotations.dart';
-import 'package:mockito/mockito.dart';
-import 'package:playground_components/src/models/sdk.dart';
-import 'package:playground_components/src/models/snippet_file.dart';
-import
'package:playground_components/src/repositories/code_client/code_client.dart';
-import 'package:playground_components/src/repositories/code_repository.dart';
-import
'package:playground_components/src/repositories/models/check_status_response.dart';
-import
'package:playground_components/src/repositories/models/output_response.dart';
-import
'package:playground_components/src/repositories/models/run_code_request.dart';
-import
'package:playground_components/src/repositories/models/run_code_response.dart';
-import
'package:playground_components/src/repositories/models/run_code_result.dart';
-
-import 'code_repository_test.mocks.dart';
-
-const kRequestMock = RunCodeRequest(
- files: [SnippetFile(content: 'code', isMain: true)],
- sdk: Sdk.java,
- pipelineOptions: {},
- datasets: [],
-);
-
-const kPipelineUuid = '1234';
-const kRunOutput = 'RunOutput';
-const kLogOutput = 'LogOutput';
-const kCompileOutput = 'CompileOutput';
-const kGraphOutput = 'GraphOutput';
-const kRunErrorOutput = 'RunErrorOutput';
-const kPreparationErrorOutput = 'PreparationErrorOutput';
-const kValidationErrorOutput = 'ValidationErrorOutput';
-
-const kRunCodeResponse = RunCodeResponse(pipelineUuid: kPipelineUuid);
-const kFinishedStatusResponse = CheckStatusResponse(
- status: RunCodeStatus.finished,
-);
-const kErrorStatusResponse = CheckStatusResponse(
- status: RunCodeStatus.unknownError,
-);
-const kRunErrorStatusResponse = CheckStatusResponse(
- status: RunCodeStatus.runError,
-);
-const kExecutingStatusResponse = CheckStatusResponse(
- status: RunCodeStatus.executing,
-);
-const kCompileErrorStatusResponse = CheckStatusResponse(
- status: RunCodeStatus.compileError,
-);
-const kValidationErrorStatusResponse = CheckStatusResponse(
- status: RunCodeStatus.validationError,
-);
-const kPreparationErrorStatusResponse = CheckStatusResponse(
- status: RunCodeStatus.preparationError,
-);
-
-const kRunOutputResponse = OutputResponse(output: kRunOutput);
-const kLogOutputResponse = OutputResponse(output: kLogOutput);
-const kCompileOutputResponse = OutputResponse(output: kCompileOutput);
-const kRunErrorOutputResponse = OutputResponse(output: kRunErrorOutput);
-const kGraphResponse = OutputResponse(output: kGraphOutput);
-
-const kValidationErrorOutputResponse =
- OutputResponse(output: kValidationErrorOutput);
-
-const kPreparationErrorOutputResponse =
- OutputResponse(output: kPreparationErrorOutput);
-
-@GenerateMocks([CodeClient])
-void main() {
- group('CodeRepository runCode', () {
- test('should complete finished run code request', () async {
- // stubs
- final client = MockCodeClient();
- when(client.runCode(kRequestMock)).thenAnswer(
- (_) async => kRunCodeResponse,
- );
- when(client.checkStatus(kPipelineUuid)).thenAnswer(
- (_) async => kFinishedStatusResponse,
- );
- when(client.getRunOutput(kPipelineUuid)).thenAnswer(
- (_) async => kRunOutputResponse,
- );
- when(client.getCompileOutput(kPipelineUuid)).thenAnswer(
- (_) async => kCompileOutputResponse,
- );
- when(client.getRunErrorOutput(kPipelineUuid)).thenAnswer(
- (_) async => kRunErrorOutputResponse,
- );
- when(client.getLogOutput(kPipelineUuid)).thenAnswer(
- (_) async => kLogOutputResponse,
- );
- when(client.getGraphOutput(kPipelineUuid)).thenAnswer(
- (_) async => kGraphResponse,
- );
-
- // test variables
- final repository = CodeRepository(client: client);
- final stream = repository.runCode(kRequestMock);
-
- // test assertion
- await expectLater(
- stream,
- emitsInOrder([
- RunCodeResult(
- log: kProcessingStartedText,
- sdk: kRequestMock.sdk,
- status: RunCodeStatus.preparation,
- ),
- RunCodeResult(
- graph: kGraphOutput,
- log: kProcessingStartedText + kLogOutput,
- output: kRunOutput + kRunErrorOutput,
- pipelineUuid: kPipelineUuid,
- sdk: kRequestMock.sdk,
- status: RunCodeStatus.finished,
- ),
- ]),
- );
- // compile output should not be called
- verifyNever(client.getCompileOutput(kPipelineUuid));
- });
-
- test('should return output from compilation if failed', () async {
- // stubs
- final client = MockCodeClient();
- when(client.runCode(kRequestMock)).thenAnswer(
- (_) async => kRunCodeResponse,
- );
- when(client.checkStatus(kPipelineUuid)).thenAnswer(
- (_) async => kCompileErrorStatusResponse,
- );
- when(client.getCompileOutput(kPipelineUuid)).thenAnswer(
- (_) async => kCompileOutputResponse,
- );
- when(client.getRunOutput(kPipelineUuid)).thenAnswer(
- (_) async => kRunOutputResponse,
- );
- when(client.getLogOutput(kPipelineUuid)).thenAnswer(
- (_) async => kLogOutputResponse,
- );
- when(client.getGraphOutput(kPipelineUuid)).thenAnswer(
- (_) async => kGraphResponse,
- );
-
- // test variables
- final repository = CodeRepository(client: client);
- final stream = repository.runCode(kRequestMock);
-
- // test assertion
- await expectLater(
- stream,
- emitsInOrder([
- RunCodeResult(
- log: kProcessingStartedText,
- sdk: kRequestMock.sdk,
- status: RunCodeStatus.preparation,
- ),
- RunCodeResult(
- graph: '',
- log: kProcessingStartedText,
- output: kCompileOutput,
- pipelineUuid: kPipelineUuid,
- sdk: kRequestMock.sdk,
- status: RunCodeStatus.compileError,
- ),
- ]),
- );
- });
-
- test('should return validation error output for validation error',
- () async {
- // stubs
- final client = MockCodeClient();
- when(client.runCode(kRequestMock)).thenAnswer(
- (_) async => kRunCodeResponse,
- );
- when(client.checkStatus(kPipelineUuid)).thenAnswer(
- (_) async => kValidationErrorStatusResponse,
- );
- when(client.getValidationErrorOutput(kPipelineUuid)).thenAnswer(
- (_) async => kValidationErrorOutputResponse,
- );
- when(client.getGraphOutput(kPipelineUuid)).thenAnswer(
- (_) async => kGraphResponse,
- );
-
- // test variables
- final repository = CodeRepository(client: client);
- final stream = repository.runCode(kRequestMock);
-
- // test assertion
- await expectLater(
- stream,
- emitsInOrder([
- RunCodeResult(
- log: kProcessingStartedText,
- sdk: kRequestMock.sdk,
- status: RunCodeStatus.preparation,
- ),
- RunCodeResult(
- graph: '',
- log: kProcessingStartedText,
- output: kValidationErrorOutput,
- sdk: kRequestMock.sdk,
- status: RunCodeStatus.validationError,
- ),
- ]),
- );
- });
-
- test('should return preparation error output for preparation error',
- () async {
- // stubs
- final client = MockCodeClient();
- when(client.runCode(kRequestMock)).thenAnswer(
- (_) async => kRunCodeResponse,
- );
- when(client.checkStatus(kPipelineUuid)).thenAnswer(
- (_) async => kPreparationErrorStatusResponse,
- );
- when(client.getPreparationErrorOutput(kPipelineUuid)).thenAnswer(
- (_) async => kPreparationErrorOutputResponse,
- );
- when(client.getGraphOutput(kPipelineUuid)).thenAnswer(
- (_) async => kGraphResponse,
- );
-
- // test variables
- final repository = CodeRepository(client: client);
- final stream = repository.runCode(kRequestMock);
-
- // test assertion
- await expectLater(
- stream,
- emitsInOrder([
- RunCodeResult(
- sdk: kRequestMock.sdk,
- log: kProcessingStartedText,
- status: RunCodeStatus.preparation,
- ),
- RunCodeResult(
- graph: '',
- log: kProcessingStartedText,
- output: kPreparationErrorOutput,
- sdk: kRequestMock.sdk,
- status: RunCodeStatus.preparationError,
- ),
- ]),
- );
- });
-
- test('should return output from runError if failed while running',
- () async {
- // stubs
- final client = MockCodeClient();
- when(client.runCode(kRequestMock)).thenAnswer(
- (_) async => kRunCodeResponse,
- );
- when(client.checkStatus(kPipelineUuid)).thenAnswer(
- (_) async => kRunErrorStatusResponse,
- );
- when(client.getCompileOutput(kPipelineUuid)).thenAnswer(
- (_) async => kCompileOutputResponse,
- );
- when(client.getRunOutput(kPipelineUuid)).thenAnswer(
- (_) async => kRunOutputResponse,
- );
- when(client.getRunErrorOutput(kPipelineUuid)).thenAnswer(
- (_) async => kRunErrorOutputResponse,
- );
- when(client.getLogOutput(kPipelineUuid)).thenAnswer(
- (_) async => kLogOutputResponse,
- );
- when(client.getGraphOutput(kPipelineUuid)).thenAnswer(
- (_) async => kGraphResponse,
- );
-
- // test variables
- final repository = CodeRepository(client: client);
- final stream = repository.runCode(kRequestMock);
-
- // test assertion
- await expectLater(
- stream,
- emitsInOrder([
- RunCodeResult(
- log: kProcessingStartedText,
- sdk: kRequestMock.sdk,
- status: RunCodeStatus.preparation,
- ),
- RunCodeResult(
- graph: '',
- log: kProcessingStartedText,
- output: kRunErrorOutput,
- pipelineUuid: kPipelineUuid,
- sdk: kRequestMock.sdk,
- status: RunCodeStatus.runError,
- ),
- ]),
- );
- });
- });
-
- test('should return full output and log using streaming api when finished',
- () async {
- // stubs
- final client = MockCodeClient();
- when(client.runCode(kRequestMock)).thenAnswer(
- (_) async => kRunCodeResponse,
- );
- final answers = [
- kExecutingStatusResponse,
- kExecutingStatusResponse,
- kFinishedStatusResponse
- ];
- when(client.checkStatus(kPipelineUuid)).thenAnswer(
- (_) async => answers.removeAt(0),
- );
- when(client.getRunOutput(kPipelineUuid)).thenAnswer(
- (_) async => kRunOutputResponse,
- );
- when(client.getRunErrorOutput(kPipelineUuid)).thenAnswer(
- (_) async => kRunErrorOutputResponse,
- );
- when(client.getLogOutput(kPipelineUuid)).thenAnswer(
- (_) async => kLogOutputResponse,
- );
- when(client.getGraphOutput(kPipelineUuid)).thenAnswer(
- (_) async => kGraphResponse,
- );
-
- // test variables
- final repository = CodeRepository(client: client);
- final stream = repository.runCode(kRequestMock);
-
- // test assertion
- await expectLater(
- stream,
- emitsInOrder([
- RunCodeResult(
- log: kProcessingStartedText,
- sdk: kRequestMock.sdk,
- status: RunCodeStatus.preparation,
- ),
- RunCodeResult(
- graph: kGraphOutput,
- log: kProcessingStartedText + kLogOutput,
- output: kRunOutput,
- pipelineUuid: kPipelineUuid,
- sdk: kRequestMock.sdk,
- status: RunCodeStatus.executing,
- ),
- RunCodeResult(
- graph: kGraphOutput,
- log: kProcessingStartedText + kLogOutput * 2,
- output: kRunOutput * 2,
- pipelineUuid: kPipelineUuid,
- sdk: kRequestMock.sdk,
- status: RunCodeStatus.executing,
- ),
- RunCodeResult(
- graph: kGraphOutput,
- log: kProcessingStartedText + kLogOutput * 3,
- output: kRunOutput * 3 + kRunErrorOutput,
- pipelineUuid: kPipelineUuid,
- sdk: kRequestMock.sdk,
- status: RunCodeStatus.finished,
- ),
- ]),
- );
- });
-}
diff --git
a/playground/frontend/playground_components/test/src/repositories/code_repository_test.mocks.dart
b/playground/frontend/playground_components/test/src/repositories/code_repository_test.mocks.dart
deleted file mode 100644
index c109bae2a15..00000000000
---
a/playground/frontend/playground_components/test/src/repositories/code_repository_test.mocks.dart
+++ /dev/null
@@ -1,249 +0,0 @@
-// Mocks generated by Mockito 5.3.2 from annotations
-// in playground_components/test/src/repositories/code_repository_test.dart.
-// Do not manually edit this file.
-
-// ignore_for_file: no_leading_underscores_for_library_prefixes
-import 'dart:async' as _i7;
-
-import 'package:mockito/mockito.dart' as _i1;
-import 'package:playground_components/src/api/v1/api.pb.dart' as _i2;
-import 'package:playground_components/src/models/sdk.dart' as _i8;
-import
'package:playground_components/src/repositories/code_client/code_client.dart'
- as _i6;
-import
'package:playground_components/src/repositories/models/check_status_response.dart'
- as _i4;
-import
'package:playground_components/src/repositories/models/output_response.dart'
- as _i5;
-import
'package:playground_components/src/repositories/models/run_code_request.dart'
- as _i9;
-import
'package:playground_components/src/repositories/models/run_code_response.dart'
- as _i3;
-
-// ignore_for_file: type=lint
-// ignore_for_file: avoid_redundant_argument_values
-// ignore_for_file: avoid_setters_without_getters
-// ignore_for_file: comment_references
-// ignore_for_file: implementation_imports
-// ignore_for_file: invalid_use_of_visible_for_testing_member
-// ignore_for_file: prefer_const_constructors
-// ignore_for_file: unnecessary_parenthesis
-// ignore_for_file: camel_case_types
-// ignore_for_file: subtype_of_sealed_class
-
-class _FakeGetMetadataResponse_0 extends _i1.SmartFake
- implements _i2.GetMetadataResponse {
- _FakeGetMetadataResponse_0(
- Object parent,
- Invocation parentInvocation,
- ) : super(
- parent,
- parentInvocation,
- );
-}
-
-class _FakeRunCodeResponse_1 extends _i1.SmartFake
- implements _i3.RunCodeResponse {
- _FakeRunCodeResponse_1(
- Object parent,
- Invocation parentInvocation,
- ) : super(
- parent,
- parentInvocation,
- );
-}
-
-class _FakeCheckStatusResponse_2 extends _i1.SmartFake
- implements _i4.CheckStatusResponse {
- _FakeCheckStatusResponse_2(
- Object parent,
- Invocation parentInvocation,
- ) : super(
- parent,
- parentInvocation,
- );
-}
-
-class _FakeOutputResponse_3 extends _i1.SmartFake
- implements _i5.OutputResponse {
- _FakeOutputResponse_3(
- Object parent,
- Invocation parentInvocation,
- ) : super(
- parent,
- parentInvocation,
- );
-}
-
-/// A class which mocks [CodeClient].
-///
-/// See the documentation for Mockito's code generation for more information.
-class MockCodeClient extends _i1.Mock implements _i6.CodeClient {
- MockCodeClient() {
- _i1.throwOnMissingStub(this);
- }
-
- @override
- _i7.Future<_i2.GetMetadataResponse> getMetadata(_i8.Sdk? sdk) =>
- (super.noSuchMethod(
- Invocation.method(
- #getMetadata,
- [sdk],
- ),
- returnValue: _i7.Future<_i2.GetMetadataResponse>.value(
- _FakeGetMetadataResponse_0(
- this,
- Invocation.method(
- #getMetadata,
- [sdk],
- ),
- )),
- ) as _i7.Future<_i2.GetMetadataResponse>);
- @override
- _i7.Future<_i3.RunCodeResponse> runCode(_i9.RunCodeRequest? request) =>
- (super.noSuchMethod(
- Invocation.method(
- #runCode,
- [request],
- ),
- returnValue:
- _i7.Future<_i3.RunCodeResponse>.value(_FakeRunCodeResponse_1(
- this,
- Invocation.method(
- #runCode,
- [request],
- ),
- )),
- ) as _i7.Future<_i3.RunCodeResponse>);
- @override
- _i7.Future<void> cancelExecution(String? pipelineUuid) =>
(super.noSuchMethod(
- Invocation.method(
- #cancelExecution,
- [pipelineUuid],
- ),
- returnValue: _i7.Future<void>.value(),
- returnValueForMissingStub: _i7.Future<void>.value(),
- ) as _i7.Future<void>);
- @override
- _i7.Future<_i4.CheckStatusResponse> checkStatus(String? pipelineUuid) =>
- (super.noSuchMethod(
- Invocation.method(
- #checkStatus,
- [pipelineUuid],
- ),
- returnValue: _i7.Future<_i4.CheckStatusResponse>.value(
- _FakeCheckStatusResponse_2(
- this,
- Invocation.method(
- #checkStatus,
- [pipelineUuid],
- ),
- )),
- ) as _i7.Future<_i4.CheckStatusResponse>);
- @override
- _i7.Future<_i5.OutputResponse> getCompileOutput(String? pipelineUuid) =>
- (super.noSuchMethod(
- Invocation.method(
- #getCompileOutput,
- [pipelineUuid],
- ),
- returnValue:
_i7.Future<_i5.OutputResponse>.value(_FakeOutputResponse_3(
- this,
- Invocation.method(
- #getCompileOutput,
- [pipelineUuid],
- ),
- )),
- ) as _i7.Future<_i5.OutputResponse>);
- @override
- _i7.Future<_i5.OutputResponse> getRunOutput(String? pipelineUuid) =>
- (super.noSuchMethod(
- Invocation.method(
- #getRunOutput,
- [pipelineUuid],
- ),
- returnValue:
_i7.Future<_i5.OutputResponse>.value(_FakeOutputResponse_3(
- this,
- Invocation.method(
- #getRunOutput,
- [pipelineUuid],
- ),
- )),
- ) as _i7.Future<_i5.OutputResponse>);
- @override
- _i7.Future<_i5.OutputResponse> getLogOutput(String? pipelineUuid) =>
- (super.noSuchMethod(
- Invocation.method(
- #getLogOutput,
- [pipelineUuid],
- ),
- returnValue:
_i7.Future<_i5.OutputResponse>.value(_FakeOutputResponse_3(
- this,
- Invocation.method(
- #getLogOutput,
- [pipelineUuid],
- ),
- )),
- ) as _i7.Future<_i5.OutputResponse>);
- @override
- _i7.Future<_i5.OutputResponse> getRunErrorOutput(String? pipelineUuid) =>
- (super.noSuchMethod(
- Invocation.method(
- #getRunErrorOutput,
- [pipelineUuid],
- ),
- returnValue:
_i7.Future<_i5.OutputResponse>.value(_FakeOutputResponse_3(
- this,
- Invocation.method(
- #getRunErrorOutput,
- [pipelineUuid],
- ),
- )),
- ) as _i7.Future<_i5.OutputResponse>);
- @override
- _i7.Future<_i5.OutputResponse> getValidationErrorOutput(
- String? pipelineUuid) =>
- (super.noSuchMethod(
- Invocation.method(
- #getValidationErrorOutput,
- [pipelineUuid],
- ),
- returnValue:
_i7.Future<_i5.OutputResponse>.value(_FakeOutputResponse_3(
- this,
- Invocation.method(
- #getValidationErrorOutput,
- [pipelineUuid],
- ),
- )),
- ) as _i7.Future<_i5.OutputResponse>);
- @override
- _i7.Future<_i5.OutputResponse> getPreparationErrorOutput(
- String? pipelineUuid) =>
- (super.noSuchMethod(
- Invocation.method(
- #getPreparationErrorOutput,
- [pipelineUuid],
- ),
- returnValue:
_i7.Future<_i5.OutputResponse>.value(_FakeOutputResponse_3(
- this,
- Invocation.method(
- #getPreparationErrorOutput,
- [pipelineUuid],
- ),
- )),
- ) as _i7.Future<_i5.OutputResponse>);
- @override
- _i7.Future<_i5.OutputResponse> getGraphOutput(String? pipelineUuid) =>
- (super.noSuchMethod(
- Invocation.method(
- #getGraphOutput,
- [pipelineUuid],
- ),
- returnValue:
_i7.Future<_i5.OutputResponse>.value(_FakeOutputResponse_3(
- this,
- Invocation.method(
- #getGraphOutput,
- [pipelineUuid],
- ),
- )),
- ) as _i7.Future<_i5.OutputResponse>);
-}
diff --git
a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart
b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart
index 83c7f416ba8..507f3bdaaae 100644
--- a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart
+++ b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart
@@ -138,7 +138,7 @@ extension WidgetTesterExtension on WidgetTester {
expect(codeRunner.isCodeRunning, false);
expect(
PlaygroundComponents.analyticsService.lastEvent,
- isA<RunStartedAnalyticsEvent>(), // Cached finish does not fire events.
+ isA<RunFinishedAnalyticsEvent>(),
);
await pumpAndSettle(); // Let the UI catch up.
@@ -147,7 +147,6 @@ extension WidgetTesterExtension on WidgetTester {
expectOutput(example, this);
}
- /// Runs and expects that the execution is as fast as it should be for cache.
Future<void> modifyRunExpectReal(ExampleDescriptor example) async {
modifyCodeController();
@@ -162,6 +161,9 @@ extension WidgetTesterExtension on WidgetTester {
expect(actualText, isNot(startsWith(kCachedResultsLog)));
expectOutput(example, this);
+ // Animation stops just before the analytics event is fired, wait a bit.
+ await Future.delayed(const Duration(seconds: 1));
+
final event = PlaygroundComponents.analyticsService.lastEvent;
expect(event, isA<RunFinishedAnalyticsEvent>());