version_test.dart 11.7 KB
Newer Older
1 2 3 4 5 6
// Copyright 2017 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:convert';

7
import 'package:collection/collection.dart' show ListEquality;
8 9 10 11 12 13 14 15 16 17 18
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import 'package:quiver/time.dart';
import 'package:test/test.dart';

import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/version.dart';

19
import 'src/context.dart';
20 21 22 23 24 25 26 27 28

const JsonEncoder _kPrettyJsonEncoder = const JsonEncoder.withIndent('  ');
final Clock _testClock = new Clock.fixed(new DateTime(2015, 1, 1));
final DateTime _upToDateVersion = _testClock.agoBy(FlutterVersion.kVersionAgeConsideredUpToDate ~/ 2);
final DateTime _outOfDateVersion = _testClock.agoBy(FlutterVersion.kVersionAgeConsideredUpToDate * 2);
final DateTime _stampUpToDate = _testClock.agoBy(FlutterVersion.kCheckAgeConsideredUpToDate ~/ 2);
final DateTime _stampOutOfDate = _testClock.agoBy(FlutterVersion.kCheckAgeConsideredUpToDate * 2);

void main() {
29
  group('$FlutterVersion', () {
30 31
    setUpAll(() {
      Cache.disableLocking();
32
      FlutterVersion.kPauseToLetUserReadTheMessage = Duration.ZERO;
33 34 35 36 37 38 39 40 41 42 43 44 45
    });

    testFlutterVersion('prints nothing when Flutter installation looks fresh', () async {
      fakeData(localCommitDate: _upToDateVersion);
      await FlutterVersion.instance.checkFlutterVersionFreshness();
      _expectVersionMessage('');
    });

    testFlutterVersion('prints nothing when Flutter installation looks out-of-date by is actually up-to-date', () async {
      final FlutterVersion version = FlutterVersion.instance;

      fakeData(
        localCommitDate: _outOfDateVersion,
46
        stamp: new VersionCheckStamp(
47 48 49 50 51
          lastTimeVersionWasChecked: _stampOutOfDate,
          lastKnownRemoteVersion: _outOfDateVersion,
        ),
        remoteCommitDate: _outOfDateVersion,
        expectSetStamp: true,
52
        expectServerPing: true,
53 54 55 56 57 58 59 60 61 62 63
      );

      await version.checkFlutterVersionFreshness();
      _expectVersionMessage('');
    });

    testFlutterVersion('does not ping server when version stamp is up-to-date', () async {
      final FlutterVersion version = FlutterVersion.instance;

      fakeData(
        localCommitDate: _outOfDateVersion,
64
        stamp: new VersionCheckStamp(
65 66 67
          lastTimeVersionWasChecked: _stampUpToDate,
          lastKnownRemoteVersion: _upToDateVersion,
        ),
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
        expectSetStamp: true,
      );

      await version.checkFlutterVersionFreshness();
      _expectVersionMessage(FlutterVersion.versionOutOfDateMessage(_testClock.now().difference(_outOfDateVersion)));
    });

    testFlutterVersion('does not print warning if printed recently', () async {
      final FlutterVersion version = FlutterVersion.instance;

      fakeData(
          localCommitDate: _outOfDateVersion,
          stamp: new VersionCheckStamp(
              lastTimeVersionWasChecked: _stampUpToDate,
              lastKnownRemoteVersion: _upToDateVersion,
          ),
          expectSetStamp: true,
85 86 87 88
      );

      await version.checkFlutterVersionFreshness();
      _expectVersionMessage(FlutterVersion.versionOutOfDateMessage(_testClock.now().difference(_outOfDateVersion)));
89 90 91 92
      expect((await VersionCheckStamp.load()).lastTimeWarningWasPrinted, _testClock.now());

      await version.checkFlutterVersionFreshness();
      _expectVersionMessage('');
93 94
    });

95
    testFlutterVersion('pings server when version stamp is missing then does not', () async {
96 97 98 99 100 101
      final FlutterVersion version = FlutterVersion.instance;

      fakeData(
          localCommitDate: _outOfDateVersion,
          remoteCommitDate: _upToDateVersion,
          expectSetStamp: true,
102
          expectServerPing: true,
103 104 105 106
      );

      await version.checkFlutterVersionFreshness();
      _expectVersionMessage(FlutterVersion.versionOutOfDateMessage(_testClock.now().difference(_outOfDateVersion)));
107 108 109 110 111 112 113 114

      // Immediate subsequent check is not expected to ping the server.
      fakeData(
        localCommitDate: _outOfDateVersion,
        stamp: await VersionCheckStamp.load(),
      );
      await version.checkFlutterVersionFreshness();
      _expectVersionMessage('');
115 116 117 118 119 120 121
    });

    testFlutterVersion('pings server when version stamp is out-of-date', () async {
      final FlutterVersion version = FlutterVersion.instance;

      fakeData(
          localCommitDate: _outOfDateVersion,
122
          stamp: new VersionCheckStamp(
123 124 125 126 127
              lastTimeVersionWasChecked: _stampOutOfDate,
              lastKnownRemoteVersion: _testClock.ago(days: 2),
          ),
          remoteCommitDate: _upToDateVersion,
          expectSetStamp: true,
128
          expectServerPing: true,
129 130 131 132 133 134 135 136 137 138 139 140
      );

      await version.checkFlutterVersionFreshness();
      _expectVersionMessage(FlutterVersion.versionOutOfDateMessage(_testClock.now().difference(_outOfDateVersion)));
    });

    testFlutterVersion('ignores network issues', () async {
      final FlutterVersion version = FlutterVersion.instance;

      fakeData(
          localCommitDate: _outOfDateVersion,
          errorOnFetch: true,
141
          expectServerPing: true,
142 143 144 145 146 147
      );

      await version.checkFlutterVersionFreshness();
      _expectVersionMessage('');
    });
  });
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225

  group('$VersionCheckStamp', () {
    void _expectDefault(VersionCheckStamp stamp) {
      expect(stamp.lastKnownRemoteVersion, isNull);
      expect(stamp.lastTimeVersionWasChecked, isNull);
      expect(stamp.lastTimeWarningWasPrinted, isNull);
    }

    testFlutterVersion('loads blank when stamp file missing', () async {
      fakeData();
      _expectDefault(await VersionCheckStamp.load());
    });

    testFlutterVersion('loads blank when stamp file is malformed JSON', () async {
      fakeData(stampJson: '<');
      _expectDefault(await VersionCheckStamp.load());
    });

    testFlutterVersion('loads blank when stamp file is well-formed but invalid JSON', () async {
      fakeData(stampJson: '[]');
      _expectDefault(await VersionCheckStamp.load());
    });

    testFlutterVersion('loads valid JSON', () async {
      fakeData(stampJson: '''
      {
        "lastKnownRemoteVersion": "${_testClock.ago(days: 1)}",
        "lastTimeVersionWasChecked": "${_testClock.ago(days: 2)}",
        "lastTimeWarningWasPrinted": "${_testClock.now()}"
      }
      ''');

      final VersionCheckStamp stamp = await VersionCheckStamp.load();
      expect(stamp.lastKnownRemoteVersion, _testClock.ago(days: 1));
      expect(stamp.lastTimeVersionWasChecked, _testClock.ago(days: 2));
      expect(stamp.lastTimeWarningWasPrinted, _testClock.now());
    });

    testFlutterVersion('stores version stamp', () async {
      fakeData(expectSetStamp: true);

      _expectDefault(await VersionCheckStamp.load());

      final VersionCheckStamp stamp = new VersionCheckStamp(
        lastKnownRemoteVersion: _testClock.ago(days: 1),
        lastTimeVersionWasChecked: _testClock.ago(days: 2),
        lastTimeWarningWasPrinted: _testClock.now(),
      );
      await stamp.store();

      final VersionCheckStamp storedStamp = await VersionCheckStamp.load();
      expect(storedStamp.lastKnownRemoteVersion, _testClock.ago(days: 1));
      expect(storedStamp.lastTimeVersionWasChecked, _testClock.ago(days: 2));
      expect(storedStamp.lastTimeWarningWasPrinted, _testClock.now());
    });

    testFlutterVersion('overwrites individual fields', () async {
      fakeData(expectSetStamp: true);

      _expectDefault(await VersionCheckStamp.load());

      final VersionCheckStamp stamp = new VersionCheckStamp(
        lastKnownRemoteVersion: _testClock.ago(days: 10),
        lastTimeVersionWasChecked: _testClock.ago(days: 9),
        lastTimeWarningWasPrinted: _testClock.ago(days: 8),
      );
      await stamp.store(
        newKnownRemoteVersion: _testClock.ago(days: 1),
        newTimeVersionWasChecked: _testClock.ago(days: 2),
        newTimeWarningWasPrinted: _testClock.now(),
      );

      final VersionCheckStamp storedStamp = await VersionCheckStamp.load();
      expect(storedStamp.lastKnownRemoteVersion, _testClock.ago(days: 1));
      expect(storedStamp.lastTimeVersionWasChecked, _testClock.ago(days: 2));
      expect(storedStamp.lastTimeWarningWasPrinted, _testClock.now());
    });
  });
226 227 228 229 230
}

void _expectVersionMessage(String message) {
  final BufferLogger logger = context[Logger];
  expect(logger.statusText.trim(), message.trim());
231
  logger.clear();
232 233 234 235 236 237 238 239 240 241 242 243 244
}

void testFlutterVersion(String description, dynamic testMethod()) {
  testUsingContext(
    description,
    testMethod,
    overrides: <Type, Generator>{
      FlutterVersion: () => new FlutterVersion(_testClock),
    },
  );
}

void fakeData({
245
  DateTime localCommitDate,
246
  DateTime remoteCommitDate,
247 248
  VersionCheckStamp stamp,
  String stampJson,
249
  bool errorOnFetch: false,
250 251
  bool expectSetStamp: false,
  bool expectServerPing: false,
252
}) {
253 254 255 256 257
  final MockProcessManager pm = new MockProcessManager();
  context.setVariable(ProcessManager, pm);

  final MockCache cache = new MockCache();
  context.setVariable(Cache, cache);
258 259 260 261 262 263 264 265 266 267

  ProcessResult success(String standardOutput) {
    return new ProcessResult(1, 0, standardOutput, '');
  }

  ProcessResult failure(int exitCode) {
    return new ProcessResult(1, exitCode, '', 'error');
  }

  when(cache.getStampFor(any)).thenAnswer((Invocation invocation) {
268
    expect(invocation.positionalArguments.single, VersionCheckStamp.kFlutterVersionCheckStampFile);
269

270 271
    if (stampJson != null)
      return stampJson;
272

273 274
    if (stamp != null)
      return JSON.encode(stamp.toJson());
275

276
    return null;
277 278 279
  });

  when(cache.setStampFor(any, any)).thenAnswer((Invocation invocation) {
280
    expect(invocation.positionalArguments.first, VersionCheckStamp.kFlutterVersionCheckStampFile);
281 282

    if (expectSetStamp) {
283
      stamp = VersionCheckStamp.fromJson(JSON.decode(invocation.positionalArguments[1]));
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
      return null;
    }

    throw new StateError('Unexpected call to Cache.setStampFor(${invocation.positionalArguments}, ${invocation.namedArguments})');
  });

  final Answering syncAnswer = (Invocation invocation) {
    bool argsAre(String a1, [String a2, String a3, String a4, String a5, String a6, String a7, String a8]) {
      const ListEquality<String> equality = const ListEquality<String>();
      final List<String> args = invocation.positionalArguments.single;
      final List<String> expectedArgs =
      <String>[a1, a2, a3, a4, a5, a6, a7, a8]
          .where((String arg) => arg != null)
          .toList();
      return equality.equals(args, expectedArgs);
    }

    if (argsAre('git', 'log', '-n', '1', '--pretty=format:%ad', '--date=iso')) {
      return success(localCommitDate.toString());
    } else if (argsAre('git', 'remote')) {
      return success('');
    } else if (argsAre('git', 'remote', 'add', '__flutter_version_check__', 'https://github.com/flutter/flutter.git')) {
      return success('');
    } else if (argsAre('git', 'fetch', '__flutter_version_check__', 'master')) {
308 309
      if (!expectServerPing)
        fail('Did not expect server ping');
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
      return errorOnFetch ? failure(128) : success('');
    } else if (remoteCommitDate != null && argsAre('git', 'log', '__flutter_version_check__/master', '-n', '1', '--pretty=format:%ad', '--date=iso')) {
      return success(remoteCommitDate.toString());
    }

    throw new StateError('Unexpected call to ProcessManager.run(${invocation.positionalArguments}, ${invocation.namedArguments})');
  };

  when(pm.runSync(any, workingDirectory: any)).thenAnswer(syncAnswer);
  when(pm.run(any, workingDirectory: any)).thenAnswer((Invocation invocation) async {
    return syncAnswer(invocation);
  });
}

class MockProcessManager extends Mock implements ProcessManager {}
class MockCache extends Mock implements Cache {}