Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2056)

Side by Side Diff: pkg/observe/test/observe_test.dart

Issue 19771010: implement dirty checking for @observable objects (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: logging for loops in dirty checking Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 import 'dart:async'; 5 import 'dart:async';
6 import 'package:logging/logging.dart';
6 import 'package:observe/observe.dart'; 7 import 'package:observe/observe.dart';
8 import 'package:observe/src/watcher.dart' as watcher;
7 import 'package:unittest/unittest.dart'; 9 import 'package:unittest/unittest.dart';
8 import 'utils.dart'; 10 import 'observe_test_utils.dart';
11
12 const _VALUE = const Symbol('value');
9 13
10 main() { 14 main() {
11 // Note: to test the basic Observable system, we use ObservableBox due to its 15 // Note: to test the basic Observable system, we use ObservableBox due to its
12 // simplicity. 16 // simplicity. We also test a variant that is based on dirty-checking.
13 17
14 const _VALUE = const Symbol('value'); 18 observeTest('no observers at the start', () {
15 19 expect(watcher.allObservablesCount, 0);
16 group('ObservableBox', () { 20 });
17 test('no observers', () { 21
18 var t = new ObservableBox<int>(123); 22 group('WatcherModel', () { observeTests(watch: true); });
19 expect(t.value, 123); 23
20 t.value = 42; 24 group('ObservableBox', () { observeTests(); });
21 expect(t.value, 42); 25
22 expect(t.hasObservers, false); 26 group('dirtyCheck loops can be debugged', () {
27 var messages;
28 var subscription;
29 setUp(() {
30 messages = [];
31 subscription = Logger.root.onRecord.listen((record) {
32 messages.add(record.message);
33 });
23 }); 34 });
24 35
25 test('listen adds an observer', () { 36 tearDown(() {
26 var t = new ObservableBox<int>(123); 37 subscription.cancel();
27 expect(t.hasObservers, false);
28
29 t.changes.listen((n) {});
30 expect(t.hasObservers, true);
31 }); 38 });
32 39
33 test('changes delived async', () { 40 test('logs debug information', () {
34 var t = new ObservableBox<int>(123); 41 var maxNumIterations = watcher.MAX_DIRTY_CHECK_CYCLES;
35 int called = 0; 42
36 43 var x = new WatcherModel(0);
37 t.changes.listen(expectAsync1((records) { 44 var sub = x.changes.listen(expectAsync1((_) { x.value++; },
38 called++; 45 count: maxNumIterations));
39 expectChanges(records, [_record(_VALUE), _record(_VALUE)]); 46 x.value = 1;
47 Observable.dirtyCheck();
48 expect(x.value, maxNumIterations + 1);
49 expect(messages.length, 2);
50
51 expect(messages[0], contains('Possible loop'));
52 expect(messages[1], contains('index 0'));
53 expect(messages[1], contains('object: $x'));
54
55 sub.cancel();
56 });
57 });
58 }
59
60 observeTests({bool watch: false}) {
61
62 final createModel = watch ? (x) => new WatcherModel(x)
63 : (x) => new ObservableBox(x);
64
65 // Track the subscriptions so we can clean them up in tearDown.
66 List subs;
67
68 int initialObservers;
69 setUp(() {
70 initialObservers = watcher.allObservablesCount;
71 subs = [];
72
73 if (watch) runAsync(Observable.dirtyCheck);
74 });
75
76 tearDown(() {
77 for (var sub in subs) sub.cancel();
78 performMicrotaskCheckpoint();
79
80 expect(watcher.allObservablesCount, initialObservers,
81 reason: 'Observable object leaked');
82 });
83
84 observeTest('no observers', () {
85 var t = createModel(123);
86 expect(t.value, 123);
87 t.value = 42;
88 expect(t.value, 42);
89 expect(t.hasObservers, false);
90 });
91
92 observeTest('listen adds an observer', () {
93 var t = createModel(123);
94 expect(t.hasObservers, false);
95
96 subs.add(t.changes.listen((n) {}));
97 expect(t.hasObservers, true);
98 });
99
100 observeTest('changes delived async', () {
101 var t = createModel(123);
102 int called = 0;
103
104 subs.add(t.changes.listen(expectAsync1((records) {
105 called++;
106 expectChanges(records, _changedValue(watch ? 1 : 2));
107 })));
108
109 t.value = 41;
110 t.value = 42;
111 expect(called, 0);
112 });
113
114 observeTest('cause changes in handler', () {
115 var t = createModel(123);
116 int called = 0;
117
118 subs.add(t.changes.listen(expectAsync1((records) {
119 called++;
120 expectChanges(records, _changedValue(1));
121 if (called == 1) {
122 // Cause another change
123 t.value = 777;
124 }
125 }, count: 2)));
126
127 t.value = 42;
128 });
129
130 observeTest('multiple observers', () {
131 var t = createModel(123);
132
133 verifyRecords(records) {
134 expectChanges(records, _changedValue(watch ? 1 : 2));
135 };
136
137 subs.add(t.changes.listen(expectAsync1(verifyRecords)));
138 subs.add(t.changes.listen(expectAsync1(verifyRecords)));
139
140 t.value = 41;
141 t.value = 42;
142 });
143
144 observeTest('performMicrotaskCheckpoint', () {
145 var t = createModel(123);
146 var records = [];
147 subs.add(t.changes.listen((r) { records.addAll(r); }));
148 t.value = 41;
149 t.value = 42;
150 expectChanges(records, [], reason: 'changes delived async');
151
152 performMicrotaskCheckpoint();
153 expectChanges(records, _changedValue(watch ? 1 : 2));
154 records.clear();
155
156 t.value = 777;
157 expectChanges(records, [], reason: 'changes delived async');
158
159 performMicrotaskCheckpoint();
160 expectChanges(records, _changedValue(1));
161
162 // Has no effect if there are no changes
163 performMicrotaskCheckpoint();
164 expectChanges(records, _changedValue(1));
165 });
166
167 observeTest('cancel listening', () {
168 var t = createModel(123);
169 var sub;
170 sub = t.changes.listen(expectAsync1((records) {
171 expectChanges(records, _changedValue(1));
172 sub.cancel();
173 t.value = 777;
174 runAsync(Observable.dirtyCheck);
175 }));
176 t.value = 42;
177 });
178
179 observeTest('cancel and reobserve', () {
180 var t = createModel(123);
181 var sub;
182 sub = t.changes.listen(expectAsync1((records) {
183 expectChanges(records, _changedValue(1));
184 sub.cancel();
185
186 runAsync(expectAsync0(() {
187 subs.add(t.changes.listen(expectAsync1((records) {
188 expectChanges(records, _changedValue(1));
189 })));
190 t.value = 777;
191 runAsync(Observable.dirtyCheck);
40 })); 192 }));
41 t.value = 41; 193 }));
42 t.value = 42; 194 t.value = 42;
43 expect(called, 0);
44 });
45
46 test('cause changes in handler', () {
47 var t = new ObservableBox<int>(123);
48 int called = 0;
49
50 t.changes.listen(expectAsync1((records) {
51 called++;
52 expectChanges(records, [_record(_VALUE)]);
53 if (called == 1) {
54 // Cause another change
55 t.value = 777;
56 }
57 }, count: 2));
58
59 t.value = 42;
60 });
61
62 test('multiple observers', () {
63 var t = new ObservableBox<int>(123);
64
65 verifyRecords(records) {
66 expectChanges(records, [_record(_VALUE), _record(_VALUE)]);
67 };
68
69 t.changes.listen(expectAsync1(verifyRecords));
70 t.changes.listen(expectAsync1(verifyRecords));
71
72 t.value = 41;
73 t.value = 42;
74 });
75
76 test('deliverChangeRecords', () {
77 var t = new ObservableBox<int>(123);
78 var records = [];
79 t.changes.listen((r) { records.addAll(r); });
80 t.value = 41;
81 t.value = 42;
82 expectChanges(records, [], reason: 'changes delived async');
83
84 deliverChangeRecords();
85 expectChanges(records,
86 [_record(_VALUE), _record(_VALUE)]);
87 records.clear();
88
89 t.value = 777;
90 expectChanges(records, [], reason: 'changes delived async');
91
92 deliverChangeRecords();
93 expectChanges(records, [_record(_VALUE)]);
94
95 // Has no effect if there are no changes
96 deliverChangeRecords();
97 expectChanges(records, [_record(_VALUE)]);
98 });
99
100 test('cancel listening', () {
101 var t = new ObservableBox<int>(123);
102 var sub;
103 sub = t.changes.listen(expectAsync1((records) {
104 expectChanges(records, [_record(_VALUE)]);
105 sub.cancel();
106 t.value = 777;
107 }));
108 t.value = 42;
109 });
110
111 test('cancel and reobserve', () {
112 var t = new ObservableBox<int>(123);
113 var sub;
114 sub = t.changes.listen(expectAsync1((records) {
115 expectChanges(records, [_record(_VALUE)]);
116 sub.cancel();
117
118 runAsync(expectAsync0(() {
119 sub = t.changes.listen(expectAsync1((records) {
120 expectChanges(records, [_record(_VALUE)]);
121 }));
122 t.value = 777;
123 }));
124 }));
125 t.value = 42;
126 });
127 }); 195 });
128 } 196 }
129 197
130 _record(key) => new PropertyChangeRecord(key); 198 _changedValue(len) => new List.filled(len, new PropertyChangeRecord(_VALUE));
199
200 // A test model based on dirty checking.
201 class WatcherModel<T> extends ObservableBase {
202 @observable T value;
203
204 WatcherModel([T initialValue]) : value = initialValue;
205
206 String toString() => '#<$runtimeType value: $value>';
207 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698