Commit 82f887de authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Fix Flutter Tools Tests to run on Windows (#7878)

parent 079db95b
...@@ -7,6 +7,7 @@ import 'dart:async'; ...@@ -7,6 +7,7 @@ import 'dart:async';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import '../base/common.dart'; import '../base/common.dart';
import '../base/os.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../cache.dart'; import '../cache.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
...@@ -37,8 +38,8 @@ class FormatCommand extends FlutterCommand { ...@@ -37,8 +38,8 @@ class FormatCommand extends FlutterCommand {
); );
} }
String dartfmt = path.join( String executable = os.getExecutableName('dartfmt', winExtension: 'bat');
Cache.flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', 'dartfmt'); String dartfmt = path.join(Cache.flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', executable);
List<String> cmd = <String>[dartfmt, '-w']..addAll(argResults.rest); List<String> cmd = <String>[dartfmt, '-w']..addAll(argResults.rest);
int result = await runCommandAndStreamOutput(cmd); int result = await runCommandAndStreamOutput(cmd);
if (result != 0) if (result != 0)
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
import 'dart:async'; import 'dart:async';
import 'package:path/path.dart' as path;
import '../base/common.dart'; import '../base/common.dart';
import '../base/os.dart'; import '../base/os.dart';
import '../base/process.dart'; import '../base/process.dart';
...@@ -56,9 +58,10 @@ class UpgradeCommand extends FlutterCommand { ...@@ -56,9 +58,10 @@ class UpgradeCommand extends FlutterCommand {
// if necessary. // if necessary.
printStatus(''); printStatus('');
printStatus('Upgrading engine...'); printStatus('Upgrading engine...');
String flutter = os.getExecutableName('flutter', winExtension: 'bat');
code = await runCommandAndStreamOutput( code = await runCommandAndStreamOutput(
<String>[ <String>[
'bin/flutter', '--no-color', 'precache' path.join(Cache.flutterRoot, 'bin', flutter), '--no-color', 'precache'
], ],
workingDirectory: Cache.flutterRoot, workingDirectory: Cache.flutterRoot,
allowReentrantFlutter: true allowReentrantFlutter: true
......
...@@ -46,7 +46,9 @@ class AnalysisDriver { ...@@ -46,7 +46,9 @@ class AnalysisDriver {
DriverOptions options; DriverOptions options;
String get sdkDir => options.dartSdkPath ?? cli_util.getSdkDir().path; String get sdkDir {
return options.dartSdkPath ?? path.absolute(cli_util.getSdkDir().path);
}
List<AnalysisErrorDescription> analyze(Iterable<File> files) { List<AnalysisErrorDescription> analyze(Iterable<File> files) {
List<AnalysisErrorInfo> infos = _analyze(files); List<AnalysisErrorInfo> infos = _analyze(files);
......
...@@ -544,13 +544,13 @@ class DevFS { ...@@ -544,13 +544,13 @@ class DevFS {
// This project's own package. // This project's own package.
final bool isProjectPackage = uri.toString() == 'lib/'; final bool isProjectPackage = uri.toString() == 'lib/';
final String directoryName = final String directoryName =
isProjectPackage ? 'lib' : 'packages/$packageName'; isProjectPackage ? 'lib' : path.join('packages', packageName);
// If this is the project's package, we need to pass both // If this is the project's package, we need to pass both
// package:<package_name> and lib/ as paths to be checked against // package:<package_name> and lib/ as paths to be checked against
// the filter because we must support both package: imports and relative // the filter because we must support both package: imports and relative
// path imports within the project's own code. // path imports within the project's own code.
final String packagesDirectoryName = final String packagesDirectoryName =
isProjectPackage ? 'packages/$packageName' : null; isProjectPackage ? path.join('packages', packageName) : null;
Directory directory = fs.directory(uri); Directory directory = fs.directory(uri);
bool packageExists = bool packageExists =
await _scanDirectory(directory, await _scanDirectory(directory,
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:io' as io;
import 'package:flutter_tools/src/dart/dependencies.dart'; import 'package:flutter_tools/src/dart/dependencies.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
...@@ -36,5 +38,5 @@ void main() { ...@@ -36,5 +38,5 @@ void main() {
expect(e.contains('unexpected token \'bad\''), isTrue); expect(e.contains('unexpected token \'bad\''), isTrue);
} }
}); });
}); }, skip: io.Platform.isWindows); // TODO(goderbauer): enable when sky_snapshot is available
} }
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:io' as io;
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
...@@ -16,7 +18,7 @@ import 'src/context.dart'; ...@@ -16,7 +18,7 @@ import 'src/context.dart';
void main() { void main() {
group('DependencyChecker', () { group('DependencyChecker', () {
final String basePath = path.dirname(platform.script.path); final String basePath = path.dirname(path.fromUri(platform.script));
final String dataPath = path.join(basePath, 'data', 'dart_dependencies_test'); final String dataPath = path.join(basePath, 'data', 'dart_dependencies_test');
MemoryFileSystem testFileSystem; MemoryFileSystem testFileSystem;
...@@ -97,5 +99,5 @@ void main() { ...@@ -97,5 +99,5 @@ void main() {
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => testFileSystem, FileSystem: () => testFileSystem,
}); });
}); }, skip: io.Platform.isWindows); // TODO(goderbauer): Migrate test away from 'touch' bash command.
} }
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io' as io;
import 'package:flutter_tools/src/asset.dart'; import 'package:flutter_tools/src/asset.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
...@@ -19,8 +20,8 @@ import 'src/context.dart'; ...@@ -19,8 +20,8 @@ import 'src/context.dart';
import 'src/mocks.dart'; import 'src/mocks.dart';
void main() { void main() {
final String filePath = 'bar/foo.txt'; final String filePath = path.join('bar', 'foo.txt');
final String filePath2 = 'foo/bar.txt'; final String filePath2 = path.join('foo', 'bar.txt');
Directory tempDir; Directory tempDir;
String basePath; String basePath;
DevFS devFS; DevFS devFS;
...@@ -82,8 +83,8 @@ void main() { ...@@ -82,8 +83,8 @@ void main() {
int bytes = await devFS.update(); int bytes = await devFS.update();
devFSOperations.expectMessages(<String>[ devFSOperations.expectMessages(<String>[
'writeFile test .packages', 'writeFile test .packages',
'writeFile test bar/foo.txt', 'writeFile test ${path.join('bar', 'foo.txt')}',
'writeFile test packages/somepkg/somefile.txt', 'writeFile test ${path.join('packages', 'somepkg', 'somefile.txt')}',
]); ]);
expect(devFS.assetPathsToEvict, isEmpty); expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 31); expect(bytes, 31);
...@@ -94,7 +95,7 @@ void main() { ...@@ -94,7 +95,7 @@ void main() {
file.writeAsBytesSync(<int>[1, 2, 3, 4, 5, 6, 7]); file.writeAsBytesSync(<int>[1, 2, 3, 4, 5, 6, 7]);
int bytes = await devFS.update(); int bytes = await devFS.update();
devFSOperations.expectMessages(<String>[ devFSOperations.expectMessages(<String>[
'writeFile test foo/bar.txt', 'writeFile test ${path.join('foo', 'bar.txt')}',
]); ]);
expect(devFS.assetPathsToEvict, isEmpty); expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 7); expect(bytes, 7);
...@@ -111,17 +112,17 @@ void main() { ...@@ -111,17 +112,17 @@ void main() {
await file.writeAsBytes(<int>[1, 2, 3, 4, 5, 6]); await file.writeAsBytes(<int>[1, 2, 3, 4, 5, 6]);
bytes = await devFS.update(); bytes = await devFS.update();
devFSOperations.expectMessages(<String>[ devFSOperations.expectMessages(<String>[
'writeFile test bar/foo.txt', 'writeFile test ${path.join('bar', 'foo.txt')}',
]); ]);
expect(devFS.assetPathsToEvict, isEmpty); expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 6); expect(bytes, 6);
}); }, skip: io.Platform.isWindows); // TODO(goderbauer): enable when updateFileModificationTime is ported to Windows
testUsingContext('delete a file from the local file system', () async { testUsingContext('delete a file from the local file system', () async {
File file = fs.file(path.join(basePath, filePath)); File file = fs.file(path.join(basePath, filePath));
await file.delete(); await file.delete();
int bytes = await devFS.update(); int bytes = await devFS.update();
devFSOperations.expectMessages(<String>[ devFSOperations.expectMessages(<String>[
'deleteFile test bar/foo.txt', 'deleteFile test ${path.join('bar', 'foo.txt')}',
]); ]);
expect(devFS.assetPathsToEvict, isEmpty); expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 0); expect(bytes, 0);
...@@ -131,7 +132,7 @@ void main() { ...@@ -131,7 +132,7 @@ void main() {
int bytes = await devFS.update(); int bytes = await devFS.update();
devFSOperations.expectMessages(<String>[ devFSOperations.expectMessages(<String>[
'writeFile test .packages', 'writeFile test .packages',
'writeFile test packages/newpkg/anotherfile.txt', 'writeFile test ${path.join('packages', 'newpkg', 'anotherfile.txt')}',
]); ]);
expect(devFS.assetPathsToEvict, isEmpty); expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 51); expect(bytes, 51);
...@@ -140,7 +141,7 @@ void main() { ...@@ -140,7 +141,7 @@ void main() {
assetBundle.entries['a.txt'] = new DevFSStringContent('abc'); assetBundle.entries['a.txt'] = new DevFSStringContent('abc');
int bytes = await devFS.update(bundle: assetBundle, bundleDirty: true); int bytes = await devFS.update(bundle: assetBundle, bundleDirty: true);
devFSOperations.expectMessages(<String>[ devFSOperations.expectMessages(<String>[
'writeFile test ${getAssetBuildDirectory()}/a.txt', 'writeFile test ${_inAssetBuildDirectory('a.txt')}',
]); ]);
expect(devFS.assetPathsToEvict, unorderedMatches(<String>['a.txt'])); expect(devFS.assetPathsToEvict, unorderedMatches(<String>['a.txt']));
devFS.assetPathsToEvict.clear(); devFS.assetPathsToEvict.clear();
...@@ -151,8 +152,8 @@ void main() { ...@@ -151,8 +152,8 @@ void main() {
int bytes = await devFS.update(bundle: assetBundle, bundleDirty: true); int bytes = await devFS.update(bundle: assetBundle, bundleDirty: true);
// Expect entire asset bundle written because bundleDirty is true // Expect entire asset bundle written because bundleDirty is true
devFSOperations.expectMessages(<String>[ devFSOperations.expectMessages(<String>[
'writeFile test ${getAssetBuildDirectory()}/a.txt', 'writeFile test ${_inAssetBuildDirectory('a.txt')}',
'writeFile test ${getAssetBuildDirectory()}/b.txt', 'writeFile test ${_inAssetBuildDirectory('b.txt')}',
]); ]);
expect(devFS.assetPathsToEvict, unorderedMatches(<String>[ expect(devFS.assetPathsToEvict, unorderedMatches(<String>[
'a.txt', 'b.txt'])); 'a.txt', 'b.txt']));
...@@ -163,7 +164,7 @@ void main() { ...@@ -163,7 +164,7 @@ void main() {
assetBundle.entries['c.txt'] = new DevFSStringContent('12'); assetBundle.entries['c.txt'] = new DevFSStringContent('12');
int bytes = await devFS.update(bundle: assetBundle); int bytes = await devFS.update(bundle: assetBundle);
devFSOperations.expectMessages(<String>[ devFSOperations.expectMessages(<String>[
'writeFile test ${getAssetBuildDirectory()}/c.txt', 'writeFile test ${_inAssetBuildDirectory('c.txt')}',
]); ]);
expect(devFS.assetPathsToEvict, unorderedMatches(<String>[ expect(devFS.assetPathsToEvict, unorderedMatches(<String>[
'c.txt'])); 'c.txt']));
...@@ -174,7 +175,7 @@ void main() { ...@@ -174,7 +175,7 @@ void main() {
assetBundle.entries.remove('c.txt'); assetBundle.entries.remove('c.txt');
int bytes = await devFS.update(bundle: assetBundle); int bytes = await devFS.update(bundle: assetBundle);
devFSOperations.expectMessages(<String>[ devFSOperations.expectMessages(<String>[
'deleteFile test ${getAssetBuildDirectory()}/c.txt', 'deleteFile test ${_inAssetBuildDirectory('c.txt')}',
]); ]);
expect(devFS.assetPathsToEvict, unorderedMatches(<String>['c.txt'])); expect(devFS.assetPathsToEvict, unorderedMatches(<String>['c.txt']));
devFS.assetPathsToEvict.clear(); devFS.assetPathsToEvict.clear();
...@@ -184,8 +185,8 @@ void main() { ...@@ -184,8 +185,8 @@ void main() {
assetBundle.entries.clear(); assetBundle.entries.clear();
int bytes = await devFS.update(bundle: assetBundle, bundleDirty: true); int bytes = await devFS.update(bundle: assetBundle, bundleDirty: true);
devFSOperations.expectMessages(<String>[ devFSOperations.expectMessages(<String>[
'deleteFile test ${getAssetBuildDirectory()}/a.txt', 'deleteFile test ${_inAssetBuildDirectory('a.txt')}',
'deleteFile test ${getAssetBuildDirectory()}/b.txt', 'deleteFile test ${_inAssetBuildDirectory('b.txt')}',
]); ]);
expect(devFS.assetPathsToEvict, unorderedMatches(<String>[ expect(devFS.assetPathsToEvict, unorderedMatches(<String>[
'a.txt', 'b.txt' 'a.txt', 'b.txt'
...@@ -231,8 +232,8 @@ void main() { ...@@ -231,8 +232,8 @@ void main() {
int bytes = await devFS.update(); int bytes = await devFS.update();
vmService.expectMessages(<String>[ vmService.expectMessages(<String>[
'writeFile test .packages', 'writeFile test .packages',
'writeFile test bar/foo.txt', 'writeFile test ${path.join('bar', 'foo.txt')}',
'writeFile test packages/somepkg/somefile.txt', 'writeFile test ${path.join('packages', 'somepkg', 'somefile.txt')}',
]); ]);
expect(devFS.assetPathsToEvict, isEmpty); expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 31); expect(bytes, 31);
...@@ -334,7 +335,10 @@ Future<Null> _createPackage(String pkgName, String pkgFileName) async { ...@@ -334,7 +335,10 @@ Future<Null> _createPackage(String pkgName, String pkgFileName) async {
_packages[pkgName] = pkgTempDir; _packages[pkgName] = pkgTempDir;
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
_packages.forEach((String pkgName, Directory pkgTempDir) { _packages.forEach((String pkgName, Directory pkgTempDir) {
sb.writeln('$pkgName:${pkgTempDir.path}/$pkgName/lib'); Uri pkgPath = path.toUri(path.join(pkgTempDir.path, pkgName, 'lib'));
sb.writeln('$pkgName:$pkgPath');
}); });
fs.file(path.join(_tempDirs[0].path, '.packages')).writeAsStringSync(sb.toString()); fs.file(path.join(_tempDirs[0].path, '.packages')).writeAsStringSync(sb.toString());
} }
String _inAssetBuildDirectory(String filename) => path.join(getAssetBuildDirectory(), filename);
\ No newline at end of file
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:io' as io;
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_device.dart'; import 'package:flutter_tools/src/android/android_device.dart';
import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/common.dart';
...@@ -298,7 +300,7 @@ void main() { ...@@ -298,7 +300,7 @@ void main() {
FileSystem: () => memoryFileSystem, FileSystem: () => memoryFileSystem,
}); });
}); });
}); }, skip: io.Platform.isWindows); // TODO(goderbauer): enable when drive command is working
} }
class MockDevice extends Mock implements Device { class MockDevice extends Mock implements Device {
......
...@@ -4,16 +4,18 @@ ...@@ -4,16 +4,18 @@
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart'; import 'package:test/test.dart';
void main() { void main() {
setUp(() { setUp(() {
String flutterRoot = platform.environment['FLUTTER_ROOT']; String flutterTools = path.join(platform.environment['FLUTTER_ROOT'],
assert(fs.currentDirectory.path == '$flutterRoot/packages/flutter_tools'); 'packages', 'flutter_tools');
assert(path.equals(fs.currentDirectory.path, flutterTools));
}); });
test('no unauthorized imports of dart:io', () { test('no unauthorized imports of dart:io', () {
for (String path in <String>['lib', 'bin', 'test']) { for (String path in <String>['lib', 'bin']) {
fs.directory(path) fs.directory(path)
.listSync(recursive: true) .listSync(recursive: true)
.where(_isDartFile) .where(_isDartFile)
...@@ -36,6 +38,6 @@ bool _isDartFile(FileSystemEntity entity) => ...@@ -36,6 +38,6 @@ bool _isDartFile(FileSystemEntity entity) =>
entity is File && entity.path.endsWith('.dart'); entity is File && entity.path.endsWith('.dart');
bool _isNotWhitelisted(FileSystemEntity entity) => bool _isNotWhitelisted(FileSystemEntity entity) =>
entity.path != 'lib/src/base/io.dart'; entity.path != path.join('lib', 'src', 'base', 'io.dart');
File _asFile(FileSystemEntity entity) => entity; File _asFile(FileSystemEntity entity) => entity;
...@@ -35,7 +35,8 @@ typedef dynamic Generator(); ...@@ -35,7 +35,8 @@ typedef dynamic Generator();
void testUsingContext(String description, dynamic testMethod(), { void testUsingContext(String description, dynamic testMethod(), {
Timeout timeout, Timeout timeout,
Map<Type, Generator> overrides: const <Type, Generator>{} Map<Type, Generator> overrides: const <Type, Generator>{},
bool skip: false,
}) { }) {
test(description, () async { test(description, () async {
AppContext testContext = new AppContext(); AppContext testContext = new AppContext();
...@@ -69,7 +70,7 @@ void testUsingContext(String description, dynamic testMethod(), { ...@@ -69,7 +70,7 @@ void testUsingContext(String description, dynamic testMethod(), {
testContext.putIfAbsent(SimControl, () => new MockSimControl()); testContext.putIfAbsent(SimControl, () => new MockSimControl());
testContext.putIfAbsent(Usage, () => new MockUsage()); testContext.putIfAbsent(Usage, () => new MockUsage());
final String basePath = path.dirname(platform.script.path); final String basePath = path.dirname(path.fromUri(platform.script));
final String flutterRoot = final String flutterRoot =
path.normalize(path.join(basePath, '..', '..', '..')); path.normalize(path.join(basePath, '..', '..', '..'));
try { try {
...@@ -96,7 +97,7 @@ void testUsingContext(String description, dynamic testMethod(), { ...@@ -96,7 +97,7 @@ void testUsingContext(String description, dynamic testMethod(), {
rethrow; rethrow;
} }
}, timeout: timeout); }, timeout: timeout, skip: skip);
} }
class MockDeviceManager implements DeviceManager { class MockDeviceManager implements DeviceManager {
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:io' as io;
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
...@@ -33,7 +34,7 @@ void main() { ...@@ -33,7 +34,7 @@ void main() {
Cache.flutterRoot = '../..'; Cache.flutterRoot = '../..';
return _testFile('trivial', 1, missingDependencyTests, missingDependencyTests); return _testFile('trivial', 1, missingDependencyTests, missingDependencyTests);
}); });
}); }, skip: io.Platform.isWindows); // TODO(goderbauer): enable when sky_shell is available
} }
Future<Null> _testFile(String testName, int wantedExitCode, String workingDirectory, String testDirectory) async { Future<Null> _testFile(String testName, int wantedExitCode, String workingDirectory, String testDirectory) async {
......
...@@ -6,6 +6,7 @@ import 'package:flutter_tools/src/base/file_system.dart'; ...@@ -6,6 +6,7 @@ import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/toolchain.dart'; import 'package:flutter_tools/src/toolchain.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'src/context.dart'; import 'src/context.dart';
...@@ -20,11 +21,11 @@ void main() { ...@@ -20,11 +21,11 @@ void main() {
expect( expect(
toolConfig.getEngineArtifactsDirectory(TargetPlatform.android_arm, BuildMode.debug).path, toolConfig.getEngineArtifactsDirectory(TargetPlatform.android_arm, BuildMode.debug).path,
endsWith('cache/artifacts/engine/android-arm') endsWith(path.join('cache', 'artifacts', 'engine', 'android-arm'))
); );
expect( expect(
toolConfig.getEngineArtifactsDirectory(TargetPlatform.android_arm, BuildMode.release).path, toolConfig.getEngineArtifactsDirectory(TargetPlatform.android_arm, BuildMode.release).path,
endsWith('cache/artifacts/engine/android-arm-release') endsWith(path.join('cache', 'artifacts', 'engine', 'android-arm-release'))
); );
expect(tempDir, isNotNull); expect(tempDir, isNotNull);
tempDir.deleteSync(recursive: true); tempDir.deleteSync(recursive: true);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment