blob: d3893b09c2257d05ea2729ec047235912d3654f7 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library tests.dart2js.lookup_map_test;
import 'package:test/test.dart';
import 'compiler_helper.dart';
main() {
Map<String, String> testDeclarations = {
'types': r'''
import 'package:lookup_map/lookup_map.dart';
class A {}
class B {}
class C {}
class D {}
class E {}''',
'const keys': r'''
import 'package:lookup_map/lookup_map.dart';
class Key { final name; const Key(this.name); }
const A = const Key("A");
const B = const Key("B");
const C = const Key("C");
const D = const Key("D");
const E = const Key("E");''',
'mixed keys': r'''
import 'package:lookup_map/lookup_map.dart';
class Key { final name; const Key(this.name); }
const A = const Key("A");
class B {}
const C = const Key("C");
class D {}
const E = const Key("E");''',
};
testDeclarations.forEach((name, declarations) {
group(name, () => _commonTests(declarations));
});
group('generic', _genericTests);
group('metadata', _metadataTests);
group('unsupported', _unsupportedKeysTests);
group('mirrors', _mirrorsTests);
}
/// Common tests for both declarations that use Types or other const expressions
/// as keys. The argument [declaration] should contain a declaration for
/// constant keys named `A`, `B`, `C`, `D`, and `E`.
_commonTests(String declarations) {
test('live entries are kept', () async {
String generated = await compileAll("""
$declarations
const map = const LookupMap(const [
A, "the-text-for-A",
]);
main() => print(map[A]);
""");
expect(generated, contains("the-text-for-A"));
});
test('live entries are kept - single-pair', () async {
String generated = await compileAll("""
$declarations
const map = const LookupMap.pair(A, "the-text-for-A");
main() => print(map[A]);
""");
expect(generated, contains("the-text-for-A"));
});
test('unused entries are removed', () async {
String generated = await compileAll("""
$declarations
const map = const LookupMap(const [
A, "the-text-for-A",
B, "the-text-for-B",
]);
main() => print(map[A]);
""");
expect(generated, isNot(contains("the-text-for-B")));
});
test('unused entries are removed - nested maps', () async {
String generated = await compileAll("""
$declarations
const map = const LookupMap(const [], const [
const LookupMap(const [
A, "the-text-for-A",
B, "the-text-for-B",
]),
]);
main() => print(map[A]);
""");
expect(generated, isNot(contains("the-text-for-B")));
});
test('unused entries are removed - single-pair', () async {
String generated = await compileAll("""
$declarations
const map = const LookupMap.pair(A, "the-text-for-A");
main() => print(map[A]);
""");
expect(generated, isNot(contains("the-text-for-B")));
});
test('unused entries are removed - nested single-pair', () async {
String generated = await compileAll("""
import 'package:lookup_map/lookup_map.dart';
$declarations
const map = const LookupMap(const [], const [
const LookupMap.pair(A, "the-text-for-A"),
const LookupMap.pair(B, "the-text-for-B"),
]);
main() => print(map[A]);
""");
expect(generated, isNot(contains("the-text-for-B")));
});
test('works if entries are declared separate from map', () async {
String generated = await compileAll("""
$declarations
const entries = const [
A, "the-text-for-A",
B, "the-text-for-B",
];
const map = const LookupMap(entries);
main() => print(map[A]);
""");
expect(generated, isNot(contains("the-text-for-B")));
});
test('escaping entries disable tree-shaking', () async {
String generated = await compileAll("""
$declarations
const entries = const [
A, "the-text-for-A",
B, "the-text-for-B",
];
const map = const LookupMap(entries);
main() {
entries.forEach(print);
print(map[A]);
}
""");
expect(generated, contains("the-text-for-B"));
});
test('uses include recursively reachable data', () async {
String generated = await compileAll("""
$declarations
const map = const LookupMap(const [
A, const ["the-text-for-A", B],
B, const ["the-text-for-B", C],
C, const ["the-text-for-C"],
D, const ["the-text-for-D", E],
E, const ["the-text-for-E"],
]);
main() => print(map[map[A][1]]);
""");
expect(generated, contains("the-text-for-A"));
expect(generated, contains("the-text-for-B"));
expect(generated, contains("the-text-for-C"));
expect(generated, isNot(contains("the-text-for-D")));
expect(generated, isNot(contains("the-text-for-E")));
});
test('uses are found through newly discovered code', () async {
String generated = await compileAll("""
$declarations
f1() => map[B][1]();
f2() => E;
const map = const LookupMap(const [
A, const ["the-text-for-A", f1],
B, const ["the-text-for-B", f2],
C, const ["the-text-for-C"],
D, const ["the-text-for-D"],
E, const ["the-text-for-E"],
]);
main() => print(map[A][1]());
""");
expect(generated, contains("the-text-for-A"));
expect(generated, contains("the-text-for-B"));
expect(generated, isNot(contains("the-text-for-C")));
expect(generated, isNot(contains("the-text-for-C")));
expect(generated, contains("the-text-for-E"));
});
test('support subclassing LookupMap', () async {
String generated = await compileAll("""
$declarations
class S extends LookupMap {
const S(list) : super(list);
}
const map = const S(const [
A, "the-text-for-A",
B, "the-text-for-B",
]);
main() => print(map[A]);
""");
expect(generated, contains("the-text-for-A"));
expect(generated, isNot(contains("the-text-for-B")));
});
test('constants keys are processed recursively', () async {
String generated = await compileAll("""
$declarations
const nested = const [ B ];
const map = const LookupMap(const [
A, "the-text-for-A",
B, "the-text-for-B",
]);
main() => print(map[nested]);
""");
expect(generated, isNot(contains("the-text-for-A")));
expect(generated, contains("the-text-for-B"));
});
}
/// Tests specific to type keys, we ensure that generic type arguments are
/// considered.
_genericTests() {
test('generic type allocations are considered used', () async {
String generated = await compileAll(r"""
import 'package:lookup_map/lookup_map.dart';
class A{}
class M<T>{ get type => T; }
const map = const LookupMap(const [
A, "the-text-for-A",
]);
main() => print(map[new M<A>().type]);
""");
expect(generated, contains("the-text-for-A"));
});
test('generics in type signatures are ignored', () async {
String generated = await compileAll(r"""
import 'package:lookup_map/lookup_map.dart';
class A{}
class B{}
class M<T>{ get type => T; }
_factory(M<B> t) => t;
const map = const LookupMap(const [
A, const ["the-text-for-A", _factory],
B, "the-text-for-B",
]);
main() => print(map[A]);
""");
expect(generated, isNot(contains("the-text-for-B")));
});
// regression test for a failure when looking up `dynamic` in a generic.
test('do not choke with dynamic type arguments', () async {
await compileAll(r"""
import 'package:lookup_map/lookup_map.dart';
class A{}
class M<T>{ get type => T; }
const map = const LookupMap(const [
A, "the-text-for-A",
]);
main() => print(map[new M<dynamic>().type]);
""");
});
}
/// Sanity checks about metadata: it is ignored for codegen even though it is
/// visited during resolution.
_metadataTests() {
test('metadata is ignored', () async {
String generated = await compileAll(r"""
import 'package:lookup_map/lookup_map.dart';
class A{ const A(); }
@A()
class M {}
const map = const LookupMap(const [
A, "the-text-for-A",
]);
main() => print(map[M]);
""");
expect(generated, isNot(contains("the-text-for-A")));
});
test('shared constants used in metadata are ignored', () async {
String generated = await compileAll(r"""
import 'package:lookup_map/lookup_map.dart';
const annot = const B(foo: A);
@B(foo: annot)
class A{ const A(); }
class B{ final Type foo; const B({this.foo}); }
class M {}
const map = const LookupMap(const [
A, const ["the-text-for-A", annot]
]);
main() => print(map[M]);
""");
expect(generated, isNot(contains("the-text-for-A")));
});
}
_unsupportedKeysTests() {
test('primitive and string keys are always kept', () async {
String generated = await compileAll("""
import 'package:lookup_map/lookup_map.dart';
const A = "A";
const B = "B";
const map = const LookupMap(const [
A, "the-text-for-A",
B, "the-text-for-B",
3, "the-text-for-3",
1.1, "the-text-for-1.1",
false, "the-text-for-false",
]);
main() => print(map[A]);
""");
expect(generated, contains("the-text-for-A"));
expect(generated, contains("the-text-for-B"));
expect(generated, contains("the-text-for-3"));
expect(generated, contains("the-text-for-1.1"));
expect(generated, contains("the-text-for-false"));
});
test('non-type const keys implementing equals are not removed', () async {
String generated = await compileAll("""
import 'package:lookup_map/lookup_map.dart';
class Key {
final name;
const Key(this.name);
int get hashCode => name.hashCode * 13;
operator ==(other) => other is Key && name == other.name;
}
const A = const Key("A");
const B = const Key("B");
const map = const LookupMap(const [
A, "the-text-for-A",
B, "the-text-for-B",
]);
main() => print(map[A]);
""");
expect(generated, contains("the-text-for-B"));
});
}
_mirrorsTests() {
test('retain entries if mirrors keep the type', () async {
String generated = await compileAll("""
import 'dart:mirrors';
import 'package:lookup_map/lookup_map.dart';
class A {}
class B {}
class C {}
const map = const LookupMap(const [
A, "the-text-for-A",
B, "the-text-for-B",
C, "the-text-for-C",
]);
main() {
reflectType(A);
print(map[A]);
}
""");
expect(generated, contains("the-text-for-A"));
expect(generated, contains("the-text-for-B"));
expect(generated, contains("the-text-for-C"));
});
test('exclude entries if MirrorsUsed also exclude the type', () async {
String generated = await compileAll("""
library foo;
@MirrorsUsed(targets: const [B])
import 'dart:mirrors';
import 'package:lookup_map/lookup_map.dart';
class A {}
class B {}
class C {}
const map = const LookupMap(const [
A, "the-text-for-A",
B, "the-text-for-B",
C, "the-text-for-C",
]);
main() {
reflectType(A);
print(map[A]);
}
""");
expect(generated, contains("the-text-for-A"));
expect(generated, contains("the-text-for-B"));
expect(generated, isNot(contains("the-text-for-C")));
});
}