packages_test.dart 9.25 KB
Newer Older
1 2 3 4 5
// Copyright 2016 The Chromium 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 'dart:async';
6 7
import 'dart:convert';
import 'dart:io' show IOSink;
8 9

import 'package:args/command_runner.dart';
10
import 'package:flutter_tools/src/base/file_system.dart' hide IOSink;
11
import 'package:flutter_tools/src/base/io.dart';
12
import 'package:flutter_tools/src/cache.dart';
13
import 'package:flutter_tools/src/commands/packages.dart';
14
import 'package:process/process.dart';
15 16
import 'package:test/test.dart';

17 18
import '../src/common.dart';
import '../src/context.dart';
19 20

void main() {
21 22
  Cache.disableLocking();
  group('packages get/upgrade', () {
23 24 25
    Directory temp;

    setUp(() {
26
      temp = fs.systemTempDirectory.createTempSync('flutter_tools');
27 28 29 30 31 32
    });

    tearDown(() {
      temp.deleteSync(recursive: true);
    });

33 34
    Future<String> runCommand(String verb, { List<String> args }) async {
      final String projectPath = await createProject(temp);
35

36 37
      final PackagesCommand command = new PackagesCommand();
      final CommandRunner<Null> runner = createTestCommandRunner(command);
38

39 40 41
      final List<String> commandArgs = <String>['packages', verb];
      if (args != null)
        commandArgs.addAll(args);
42
      commandArgs.add(projectPath);
43 44

      await runner.run(commandArgs);
45 46

      return projectPath;
47 48
    }

49 50
    void expectExists(String projectPath, String relPath) {
      expect(fs.isFileSync(fs.path.join(projectPath, relPath)), true);
51 52 53 54
    }

    // Verify that we create a project that is well-formed.
    testUsingContext('get', () async {
55 56 57
      final String projectPath = await runCommand('get');
      expectExists(projectPath, 'lib/main.dart');
      expectExists(projectPath, '.packages');
58
    }, timeout: allowForRemotePubInvocation);
59

60
    testUsingContext('get --offline', () async {
61 62 63
      final String projectPath = await runCommand('get', args: <String>['--offline']);
      expectExists(projectPath, 'lib/main.dart');
      expectExists(projectPath, '.packages');
64 65
    });

66
    testUsingContext('upgrade', () async {
67 68 69
      final String projectPath = await runCommand('upgrade');
      expectExists(projectPath, 'lib/main.dart');
      expectExists(projectPath, '.packages');
70
    }, timeout: allowForRemotePubInvocation);
71
  });
72 73

  group('packages test/pub', () {
74 75 76 77 78 79 80 81
    MockProcessManager mockProcessManager;
    MockStdio mockStdio;

    setUp(() {
      mockProcessManager = new MockProcessManager();
      mockStdio = new MockStdio();
    });

82 83
    testUsingContext('test', () async {
      await createTestCommandRunner(new PackagesCommand()).run(<String>['packages', 'test']);
84
      final List<String> commands = mockProcessManager.commands;
85
      expect(commands, hasLength(4));
86
      expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
87 88 89
      expect(commands[1], '--trace');
      expect(commands[2], 'run');
      expect(commands[3], 'test');
90
    }, overrides: <Type, Generator>{
91 92
      ProcessManager: () => mockProcessManager,
      Stdio: () => mockStdio,
93
    });
94

95 96
    testUsingContext('run', () async {
      await createTestCommandRunner(new PackagesCommand()).run(<String>['packages', '--verbose', 'pub', 'run', '--foo', 'bar']);
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
      final List<String> commands = mockProcessManager.commands;
      expect(commands, hasLength(4));
      expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
      expect(commands[1], 'run');
      expect(commands[2], '--foo');
      expect(commands[3], 'bar');
    }, overrides: <Type, Generator>{
      ProcessManager: () => mockProcessManager,
      Stdio: () => mockStdio,
    });

    testUsingContext('publish', () async {
      final PromptingProcess process = new PromptingProcess();
      mockProcessManager.processFactory = (List<String> commands) => process;
      final Future<Null> runPackages = createTestCommandRunner(new PackagesCommand()).run(<String>['packages', 'pub', 'publish']);
      final Future<Null> runPrompt = process.showPrompt('Proceed (y/n)? ', <String>['hello', 'world']);
      final Future<Null> simulateUserInput = new Future<Null>(() {
        mockStdio.simulateStdin('y');
      });
      await Future.wait(<Future<Null>>[runPackages, runPrompt, simulateUserInput]);
      final List<String> commands = mockProcessManager.commands;
      expect(commands, hasLength(2));
      expect(commands[0], matches(r'dart-sdk[\\/]bin[\\/]pub'));
      expect(commands[1], 'publish');
      final List<String> stdout = mockStdio.writtenToStdout;
      expect(stdout, hasLength(4));
      expect(stdout.sublist(0, 2), contains('Proceed (y/n)? '));
      expect(stdout.sublist(0, 2), contains('y\n'));
      expect(stdout[2], 'hello\n');
      expect(stdout[3], 'world\n');
127
    }, overrides: <Type, Generator>{
128 129
      ProcessManager: () => mockProcessManager,
      Stdio: () => mockStdio,
130 131 132 133
    });
  });
}

134 135
/// A strategy for creating Process objects from a list of commands.
typedef Process ProcessFactory(List<String> command);
136

137
/// A ProcessManager that starts Processes by delegating to a ProcessFactory.
138
class MockProcessManager implements ProcessManager {
139 140
  ProcessFactory processFactory = (List<String> commands) => new MockProcess();
  List<String> commands;
141 142 143 144 145 146 147 148 149 150

  @override
  Future<Process> start(
    List<dynamic> command, {
    String workingDirectory,
    Map<String, String> environment,
    bool includeParentEnvironment: true,
    bool runInShell: false,
    ProcessStartMode mode: ProcessStartMode.NORMAL,
  }) {
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 177 178 179 180 181 182 183 184 185 186 187 188 189
    commands = command;
    return new Future<Process>.value(processFactory(command));
  }

  @override
  dynamic noSuchMethod(Invocation invocation) => null;
}

/// A process that prompts the user to proceed, then asynchronously writes
/// some lines to stdout before it exits.
class PromptingProcess implements Process {
  Future<Null> showPrompt(String prompt, List<String> outputLines) async {
    _stdoutController.add(UTF8.encode(prompt));
    final List<int> bytesOnStdin = await _stdin.future;
    // Echo stdin to stdout.
    _stdoutController.add(bytesOnStdin);
    if (bytesOnStdin[0] == UTF8.encode('y')[0]) {
      for (final String line in outputLines)
        _stdoutController.add(UTF8.encode('$line\n'));
    }
    await _stdoutController.close();
  }

  final StreamController<List<int>> _stdoutController = new StreamController<List<int>>();
  final CompleterIOSink _stdin = new CompleterIOSink();

  @override
  Stream<List<int>> get stdout => _stdoutController.stream;

  @override
  Stream<List<int>> get stderr => const Stream<List<int>>.empty();

  @override
  IOSink get stdin => _stdin;

  @override
  Future<int> get exitCode async {
    await _stdoutController.done;
    return 0;
190 191 192 193 194 195
  }

  @override
  dynamic noSuchMethod(Invocation invocation) => null;
}

196
/// An inactive process that collects stdin and produces no output.
197
class MockProcess implements Process {
198 199 200 201 202
  final IOSink _stdin = new MemoryIOSink();

  @override
  Stream<List<int>> get stdout => const Stream<List<int>>.empty();

203
  @override
204
  Stream<List<int>> get stderr => const Stream<List<int>>.empty();
205 206

  @override
207
  IOSink get stdin => _stdin;
208 209 210 211 212 213 214 215

  @override
  Future<int> get exitCode => new Future<int>.value(0);

  @override
  dynamic noSuchMethod(Invocation invocation) => null;
}

216 217 218 219 220 221
/// An IOSink that completes a future with the first line written to it.
class CompleterIOSink extends MemoryIOSink {
  final Completer<List<int>> _completer = new Completer<List<int>>();

  Future<List<int>> get future => _completer.future;

222
  @override
223 224 225 226 227 228 229 230 231 232 233
  void add(List<int> data) {
    if (!_completer.isCompleted)
      _completer.complete(data);
    super.add(data);
  }
}

/// A Stdio that collects stdout and supports simulated stdin.
class MockStdio extends Stdio {
  final MemoryIOSink _stdout = new MemoryIOSink();
  final StreamController<List<int>> _stdin = new StreamController<List<int>>();
234 235

  @override
236
  IOSink get stdout => _stdout;
237 238

  @override
239 240 241 242
  Stream<List<int>> get stdin => _stdin.stream;

  void simulateStdin(String line) {
    _stdin.add(UTF8.encode('$line\n'));
243 244
  }

245
  List<String> get writtenToStdout => _stdout.writes.map(_stdout.encoding.decode).toList();
246 247
}

248 249
/// An IOSink that collects whatever is written to it.
class MemoryIOSink implements IOSink {
250
  @override
251 252 253
  Encoding encoding = UTF8;

  final List<List<int>> writes = <List<int>>[];
254 255

  @override
256 257 258
  void add(List<int> data) {
    writes.add(data);
  }
259 260

  @override
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
  Future<Null> addStream(Stream<List<int>> stream) {
    final Completer<Null> completer = new Completer<Null>();
    stream.listen((List<int> data) {
      add(data);
    }).onDone(() => completer.complete(null));
    return completer.future;
  }

  @override
  void writeCharCode(int charCode) {
    add(<int>[charCode]);
  }

  @override
  void write(Object obj) {
    add(encoding.encode('$obj'));
  }

  @override
280
  void writeln([Object obj = '']) {
281 282 283 284
    add(encoding.encode('$obj\n'));
  }

  @override
285
  void writeAll(Iterable<dynamic> objects, [String separator = '']) {
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
    bool addSeparator = false;
    for (dynamic object in objects) {
      if (addSeparator) {
        write(separator);
      }
      write(object);
      addSeparator = true;
    }
  }

  @override
  void addError(dynamic error, [StackTrace stackTrace]) {
    throw new UnimplementedError();
  }

  @override
  Future<Null> get done => close();

  @override
  Future<Null> close() async => null;

  @override
  Future<Null> flush() async => null;
309
}