]> git.apps.os.sepia.ceph.com Git - ceph.git/blob
7d0331dfe54cf1027d51d9aa43ef979ad9b202c3
[ceph.git] /
1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { ComponentFixture, TestBed } from '@angular/core/testing';
3 import { RouterTestingModule } from '@angular/router/testing';
4
5 import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
6 import { ToastrModule } from 'ngx-toastr';
7 import { of } from 'rxjs';
8
9 import { ErasureCodeProfileService } from '~/app/shared/api/erasure-code-profile.service';
10 import { CrushNode } from '~/app/shared/models/crush-node';
11 import { ErasureCodeProfile } from '~/app/shared/models/erasure-code-profile';
12 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
13 import { configureTestBed, FixtureHelper, FormHelper, Mocks } from '~/testing/unit-test-helper';
14 import { PoolModule } from '../pool.module';
15 import { ErasureCodeProfileFormModalComponent } from './erasure-code-profile-form-modal.component';
16
17 describe('ErasureCodeProfileFormModalComponent', () => {
18   let component: ErasureCodeProfileFormModalComponent;
19   let ecpService: ErasureCodeProfileService;
20   let fixture: ComponentFixture<ErasureCodeProfileFormModalComponent>;
21   let formHelper: FormHelper;
22   let fixtureHelper: FixtureHelper;
23   let data: { plugins: string[]; names: string[]; nodes: CrushNode[] };
24
25   const expectTechnique = (current: string) =>
26     expect(component.form.getValue('technique')).toBe(current);
27
28   const expectTechniques = (techniques: string[], current: string) => {
29     expect(component.techniques).toEqual(techniques);
30     expectTechnique(current);
31   };
32
33   const expectRequiredControls = (controlNames: string[]) => {
34     controlNames.forEach((name) => {
35       const value = component.form.getValue(name);
36       formHelper.expectValid(name);
37       formHelper.expectErrorChange(name, null, 'required');
38       // This way other fields won't fail through getting invalid.
39       formHelper.expectValidChange(name, value);
40     });
41     fixtureHelper.expectIdElementsVisible(controlNames, true);
42   };
43
44   configureTestBed({
45     imports: [HttpClientTestingModule, RouterTestingModule, ToastrModule.forRoot(), PoolModule],
46     providers: [ErasureCodeProfileService, NgbActiveModal]
47   });
48
49   beforeEach(() => {
50     fixture = TestBed.createComponent(ErasureCodeProfileFormModalComponent);
51     fixtureHelper = new FixtureHelper(fixture);
52     component = fixture.componentInstance;
53     formHelper = new FormHelper(component.form);
54     ecpService = TestBed.inject(ErasureCodeProfileService);
55     data = {
56       plugins: ['isa', 'jerasure', 'shec', 'lrc'],
57       names: ['ecp1', 'ecp2'],
58       /**
59        * Create the following test crush map:
60        * > default
61        * --> ssd-host
62        * ----> 3x osd with ssd
63        * --> mix-host
64        * ----> hdd-rack
65        * ------> 5x osd-rack with hdd
66        * ----> ssd-rack
67        * ------> 5x osd-rack with ssd
68        */
69       nodes: [
70         // Root node
71         Mocks.getCrushNode('default', -1, 'root', 11, [-2, -3]),
72         // SSD host
73         Mocks.getCrushNode('ssd-host', -2, 'host', 1, [1, 0, 2]),
74         Mocks.getCrushNode('osd.0', 0, 'osd', 0, undefined, 'ssd'),
75         Mocks.getCrushNode('osd.1', 1, 'osd', 0, undefined, 'ssd'),
76         Mocks.getCrushNode('osd.2', 2, 'osd', 0, undefined, 'ssd'),
77         // SSD and HDD mixed devices host
78         Mocks.getCrushNode('mix-host', -3, 'host', 1, [-4, -5]),
79         // HDD rack
80         Mocks.getCrushNode('hdd-rack', -4, 'rack', 3, [3, 4, 5, 6, 7]),
81         Mocks.getCrushNode('osd2.0', 3, 'osd-rack', 0, undefined, 'hdd'),
82         Mocks.getCrushNode('osd2.1', 4, 'osd-rack', 0, undefined, 'hdd'),
83         Mocks.getCrushNode('osd2.2', 5, 'osd-rack', 0, undefined, 'hdd'),
84         Mocks.getCrushNode('osd2.3', 6, 'osd-rack', 0, undefined, 'hdd'),
85         Mocks.getCrushNode('osd2.4', 7, 'osd-rack', 0, undefined, 'hdd'),
86         // SSD rack
87         Mocks.getCrushNode('ssd-rack', -5, 'rack', 3, [8, 9, 10, 11, 12]),
88         Mocks.getCrushNode('osd3.0', 8, 'osd-rack', 0, undefined, 'ssd'),
89         Mocks.getCrushNode('osd3.1', 9, 'osd-rack', 0, undefined, 'ssd'),
90         Mocks.getCrushNode('osd3.2', 10, 'osd-rack', 0, undefined, 'ssd'),
91         Mocks.getCrushNode('osd3.3', 11, 'osd-rack', 0, undefined, 'ssd'),
92         Mocks.getCrushNode('osd3.4', 12, 'osd-rack', 0, undefined, 'ssd')
93       ]
94     };
95     spyOn(ecpService, 'getInfo').and.callFake(() => of(data));
96     fixture.detectChanges();
97   });
98
99   it('should create', () => {
100     expect(component).toBeTruthy();
101   });
102
103   it('calls listing to get ecps on ngInit', () => {
104     expect(ecpService.getInfo).toHaveBeenCalled();
105     expect(component.names.length).toBe(2);
106   });
107
108   describe('form validation', () => {
109     it(`isn't valid if name is not set`, () => {
110       expect(component.form.invalid).toBeTruthy();
111       formHelper.setValue('name', 'someProfileName');
112       expect(component.form.valid).toBeTruthy();
113     });
114
115     it('sets name invalid', () => {
116       component.names = ['awesomeProfileName'];
117       formHelper.expectErrorChange('name', 'awesomeProfileName', 'uniqueName');
118       formHelper.expectErrorChange('name', 'some invalid text', 'pattern');
119       formHelper.expectErrorChange('name', null, 'required');
120     });
121
122     it('sets k to min error', () => {
123       formHelper.expectErrorChange('k', 1, 'min');
124     });
125
126     it('sets m to min error', () => {
127       formHelper.expectErrorChange('m', 0, 'min');
128     });
129
130     it(`should show all default form controls`, () => {
131       const showDefaults = (plugin: string) => {
132         formHelper.setValue('plugin', plugin);
133         fixtureHelper.expectIdElementsVisible(
134           [
135             'name',
136             'plugin',
137             'k',
138             'm',
139             'crushFailureDomain',
140             'crushRoot',
141             'crushDeviceClass',
142             'directory'
143           ],
144           true
145         );
146       };
147       showDefaults('jerasure');
148       showDefaults('shec');
149       showDefaults('lrc');
150       showDefaults('isa');
151     });
152
153     it('should change technique to default if not available in other plugin', () => {
154       expectTechnique('reed_sol_van');
155       formHelper.setValue('technique', 'blaum_roth');
156       expectTechnique('blaum_roth');
157       formHelper.setValue('plugin', 'isa');
158       expectTechnique('reed_sol_van');
159       formHelper.setValue('plugin', 'clay');
160       formHelper.expectValidChange('scalar_mds', 'shec');
161       expectTechnique('single');
162     });
163
164     describe(`for 'jerasure' plugin (default)`, () => {
165       it(`requires 'm' and 'k'`, () => {
166         expectRequiredControls(['k', 'm']);
167       });
168
169       it(`should show 'packetSize' and 'technique'`, () => {
170         fixtureHelper.expectIdElementsVisible(['packetSize', 'technique'], true);
171       });
172
173       it('should show available techniques', () => {
174         expectTechniques(
175           [
176             'reed_sol_van',
177             'reed_sol_r6_op',
178             'cauchy_orig',
179             'cauchy_good',
180             'liberation',
181             'blaum_roth',
182             'liber8tion'
183           ],
184           'reed_sol_van'
185         );
186       });
187
188       it(`should not show any other plugin specific form control`, () => {
189         fixtureHelper.expectIdElementsVisible(
190           ['c', 'l', 'crushLocality', 'd', 'scalar_mds'],
191           false
192         );
193       });
194
195       it('should not allow "k" to be changed more than possible', () => {
196         formHelper.expectErrorChange('k', 10, 'max');
197       });
198
199       it('should not allow "m" to be changed more than possible', () => {
200         formHelper.expectErrorChange('m', 10, 'max');
201       });
202     });
203
204     describe(`for 'isa' plugin`, () => {
205       beforeEach(() => {
206         formHelper.setValue('plugin', 'isa');
207       });
208
209       it(`does require 'm' and 'k'`, () => {
210         expectRequiredControls(['k', 'm']);
211       });
212
213       it(`should show 'technique'`, () => {
214         fixtureHelper.expectIdElementsVisible(['technique'], true);
215       });
216
217       it('should show available techniques', () => {
218         expectTechniques(['reed_sol_van', 'cauchy'], 'reed_sol_van');
219       });
220
221       it(`should not show any other plugin specific form control`, () => {
222         fixtureHelper.expectIdElementsVisible(
223           ['c', 'l', 'crushLocality', 'packetSize', 'd', 'scalar_mds'],
224           false
225         );
226       });
227
228       it('should not allow "k" to be changed more than possible', () => {
229         formHelper.expectErrorChange('k', 10, 'max');
230       });
231
232       it('should not allow "m" to be changed more than possible', () => {
233         formHelper.expectErrorChange('m', 10, 'max');
234       });
235     });
236
237     describe(`for 'lrc' plugin`, () => {
238       beforeEach(() => {
239         formHelper.setValue('plugin', 'lrc');
240         formHelper.expectValid('k');
241         formHelper.expectValid('l');
242         formHelper.expectValid('m');
243       });
244
245       it(`requires 'm', 'l' and 'k'`, () => {
246         expectRequiredControls(['k', 'm', 'l']);
247       });
248
249       it(`should show 'l' and 'crushLocality'`, () => {
250         fixtureHelper.expectIdElementsVisible(['l', 'crushLocality'], true);
251       });
252
253       it(`should not show any other plugin specific form control`, () => {
254         fixtureHelper.expectIdElementsVisible(
255           ['c', 'packetSize', 'technique', 'd', 'scalar_mds'],
256           false
257         );
258       });
259
260       it('should not allow "k" to be changed more than possible', () => {
261         formHelper.expectErrorChange('k', 10, 'max');
262       });
263
264       it('should not allow "m" to be changed more than possible', () => {
265         formHelper.expectErrorChange('m', 10, 'max');
266       });
267
268       it('should not allow "l" to be changed so that (k+m) is not a multiple of "l"', () => {
269         formHelper.expectErrorChange('l', 4, 'unequal');
270       });
271
272       it('should update validity of k and l on m change', () => {
273         formHelper.expectValidChange('m', 3);
274         formHelper.expectError('k', 'unequal');
275         formHelper.expectError('l', 'unequal');
276       });
277
278       describe('lrc calculation', () => {
279         const expectCorrectCalculation = (
280           k: number,
281           m: number,
282           l: number,
283           failedControl: string[] = []
284         ) => {
285           formHelper.setValue('k', k);
286           formHelper.setValue('m', m);
287           formHelper.setValue('l', l);
288           ['k', 'l'].forEach((name) => {
289             if (failedControl.includes(name)) {
290               formHelper.expectError(name, 'unequal');
291             } else {
292               formHelper.expectValid(name);
293             }
294           });
295         };
296
297         const tests = {
298           kFails: [
299             [2, 1, 1],
300             [2, 2, 1],
301             [3, 1, 1],
302             [3, 2, 1],
303             [3, 1, 2],
304             [3, 3, 1],
305             [3, 3, 3],
306             [4, 1, 1],
307             [4, 2, 1],
308             [4, 2, 2],
309             [4, 3, 1],
310             [4, 4, 1]
311           ],
312           lFails: [
313             [2, 1, 2],
314             [3, 2, 2],
315             [3, 1, 3],
316             [3, 2, 3],
317             [4, 1, 2],
318             [4, 3, 2],
319             [4, 3, 3],
320             [4, 1, 3],
321             [4, 4, 3],
322             [4, 1, 4],
323             [4, 2, 4],
324             [4, 3, 4]
325           ],
326           success: [
327             [2, 2, 2],
328             [2, 2, 4],
329             [3, 3, 2],
330             [3, 3, 6],
331             [4, 2, 3],
332             [4, 2, 6],
333             [4, 4, 2],
334             [4, 4, 8],
335             [4, 4, 4]
336           ]
337         };
338
339         it('tests all cases where k fails', () => {
340           tests.kFails.forEach((testCase) => {
341             expectCorrectCalculation(testCase[0], testCase[1], testCase[2], ['k']);
342           });
343         });
344
345         it('tests all cases where l fails', () => {
346           tests.lFails.forEach((testCase) => {
347             expectCorrectCalculation(testCase[0], testCase[1], testCase[2], ['k', 'l']);
348           });
349         });
350
351         it('tests all cases where everything is valid', () => {
352           tests.success.forEach((testCase) => {
353             expectCorrectCalculation(testCase[0], testCase[1], testCase[2]);
354           });
355         });
356       });
357     });
358
359     describe(`for 'shec' plugin`, () => {
360       beforeEach(() => {
361         formHelper.setValue('plugin', 'shec');
362         formHelper.expectValid('c');
363         formHelper.expectValid('m');
364         formHelper.expectValid('k');
365       });
366
367       it(`does require 'm', 'c' and 'k'`, () => {
368         expectRequiredControls(['k', 'm', 'c']);
369       });
370
371       it(`should not show any other plugin specific form control`, () => {
372         fixtureHelper.expectIdElementsVisible(
373           ['l', 'crushLocality', 'packetSize', 'technique', 'd', 'scalar_mds'],
374           false
375         );
376       });
377
378       it('should make sure that k has to be equal or greater than m', () => {
379         formHelper.expectValidChange('k', 3);
380         formHelper.expectErrorChange('k', 2, 'kLowerM');
381       });
382
383       it('should make sure that c has to be equal or less than m', () => {
384         formHelper.expectValidChange('c', 3);
385         formHelper.expectErrorChange('c', 4, 'cGreaterM');
386       });
387
388       it('should update validity of k and c on m change', () => {
389         formHelper.expectValidChange('m', 5);
390         formHelper.expectError('k', 'kLowerM');
391         formHelper.expectValid('c');
392
393         formHelper.expectValidChange('m', 1);
394         formHelper.expectError('c', 'cGreaterM');
395         formHelper.expectValid('k');
396       });
397     });
398
399     describe(`for 'clay' plugin`, () => {
400       beforeEach(() => {
401         formHelper.setValue('plugin', 'clay');
402         // Through this change d has a valid range from 4 to 7
403         formHelper.expectValidChange('k', 3);
404         formHelper.expectValidChange('m', 5);
405       });
406
407       it(`does require 'm', 'c', 'd', 'scalar_mds' and 'k'`, () => {
408         fixtureHelper.clickElement('#d-calc-btn');
409         expectRequiredControls(['k', 'm', 'd', 'scalar_mds']);
410       });
411
412       it(`should not show any other plugin specific form control`, () => {
413         fixtureHelper.expectIdElementsVisible(['l', 'crushLocality', 'packetSize', 'c'], false);
414       });
415
416       it('should show default values for d and scalar_mds', () => {
417         expect(component.form.getValue('d')).toBe(7); // (k+m-1)
418         expect(component.form.getValue('scalar_mds')).toBe('jerasure');
419       });
420
421       it('should auto change d if auto calculation is enabled (default)', () => {
422         formHelper.expectValidChange('k', 4);
423         expect(component.form.getValue('d')).toBe(8);
424       });
425
426       it('should have specific techniques for scalar_mds jerasure', () => {
427         expectTechniques(
428           ['reed_sol_van', 'reed_sol_r6_op', 'cauchy_orig', 'cauchy_good', 'liber8tion'],
429           'reed_sol_van'
430         );
431       });
432
433       it('should have specific techniques for scalar_mds isa', () => {
434         formHelper.expectValidChange('scalar_mds', 'isa');
435         expectTechniques(['reed_sol_van', 'cauchy'], 'reed_sol_van');
436       });
437
438       it('should have specific techniques for scalar_mds shec', () => {
439         formHelper.expectValidChange('scalar_mds', 'shec');
440         expectTechniques(['single', 'multiple'], 'single');
441       });
442
443       describe('Validity of d', () => {
444         beforeEach(() => {
445           // Don't automatically change d - the only way to get d invalid
446           fixtureHelper.clickElement('#d-calc-btn');
447         });
448
449         it('should not automatically change d if k or m have been changed', () => {
450           formHelper.expectValidChange('m', 4);
451           formHelper.expectValidChange('k', 5);
452           expect(component.form.getValue('d')).toBe(7);
453         });
454
455         it('should trigger dMin through change of d', () => {
456           formHelper.expectErrorChange('d', 3, 'dMin');
457         });
458
459         it('should trigger dMax through change of d', () => {
460           formHelper.expectErrorChange('d', 8, 'dMax');
461         });
462
463         it('should trigger dMin through change of k and m', () => {
464           formHelper.expectValidChange('m', 2);
465           formHelper.expectValidChange('k', 7);
466           formHelper.expectError('d', 'dMin');
467         });
468
469         it('should trigger dMax through change of m', () => {
470           formHelper.expectValidChange('m', 3);
471           formHelper.expectError('d', 'dMax');
472         });
473
474         it('should remove dMax through change of k', () => {
475           formHelper.expectValidChange('m', 3);
476           formHelper.expectError('d', 'dMax');
477           formHelper.expectValidChange('k', 5);
478           formHelper.expectValid('d');
479         });
480       });
481     });
482   });
483
484   describe('submission', () => {
485     let ecp: ErasureCodeProfile;
486     let submittedEcp: ErasureCodeProfile;
487
488     const testCreation = () => {
489       fixture.detectChanges();
490       component.onSubmit();
491       expect(ecpService.create).toHaveBeenCalledWith(submittedEcp);
492     };
493
494     const ecpChange = (attribute: string, value: string | number) => {
495       ecp[attribute] = value;
496       submittedEcp[attribute] = value;
497     };
498
499     beforeEach(() => {
500       ecp = new ErasureCodeProfile();
501       submittedEcp = new ErasureCodeProfile();
502       submittedEcp['crush-root'] = 'default';
503       submittedEcp['crush-failure-domain'] = 'osd-rack';
504       submittedEcp['packetsize'] = 2048;
505       submittedEcp['technique'] = 'reed_sol_van';
506
507       const taskWrapper = TestBed.inject(TaskWrapperService);
508       spyOn(taskWrapper, 'wrapTaskAroundCall').and.callThrough();
509       spyOn(ecpService, 'create').and.stub();
510     });
511
512     describe(`'jerasure' usage`, () => {
513       beforeEach(() => {
514         submittedEcp['plugin'] = 'jerasure';
515         ecpChange('name', 'jerasureProfile');
516         submittedEcp.k = 4;
517         submittedEcp.m = 2;
518       });
519
520       it('should be able to create a profile with only required fields', () => {
521         formHelper.setMultipleValues(ecp, true);
522         testCreation();
523       });
524
525       it(`does not create with missing 'k' or invalid form`, () => {
526         ecpChange('k', 0);
527         formHelper.setMultipleValues(ecp, true);
528         component.onSubmit();
529         expect(ecpService.create).not.toHaveBeenCalled();
530       });
531
532       it('should be able to create a profile with m, k, name, directory and packetSize', () => {
533         ecpChange('m', 3);
534         ecpChange('directory', '/different/ecp/path');
535         formHelper.setMultipleValues(ecp, true);
536         formHelper.setValue('packetSize', 8192, true);
537         ecpChange('packetsize', 8192);
538         testCreation();
539       });
540
541       it('should not send the profile with unsupported fields', () => {
542         formHelper.setMultipleValues(ecp, true);
543         formHelper.setValue('crushLocality', 'osd', true);
544         testCreation();
545       });
546     });
547
548     describe(`'isa' usage`, () => {
549       beforeEach(() => {
550         ecpChange('name', 'isaProfile');
551         ecpChange('plugin', 'isa');
552         submittedEcp.k = 7;
553         submittedEcp.m = 3;
554         delete submittedEcp.packetsize;
555       });
556
557       it('should be able to create a profile with only plugin and name', () => {
558         formHelper.setMultipleValues(ecp, true);
559         testCreation();
560       });
561
562       it('should send profile with plugin, name, failure domain and technique only', () => {
563         ecpChange('technique', 'cauchy');
564         formHelper.setMultipleValues(ecp, true);
565         formHelper.setValue('crushFailureDomain', 'osd', true);
566         submittedEcp['crush-failure-domain'] = 'osd';
567         submittedEcp['crush-device-class'] = 'ssd';
568         testCreation();
569       });
570
571       it('should not send the profile with unsupported fields', () => {
572         formHelper.setMultipleValues(ecp, true);
573         formHelper.setValue('packetSize', 'osd', true);
574         testCreation();
575       });
576     });
577
578     describe(`'lrc' usage`, () => {
579       beforeEach(() => {
580         ecpChange('name', 'lrcProfile');
581         ecpChange('plugin', 'lrc');
582         submittedEcp.k = 4;
583         submittedEcp.m = 2;
584         submittedEcp.l = 3;
585         delete submittedEcp.packetsize;
586         delete submittedEcp.technique;
587       });
588
589       it('should be able to create a profile with only required fields', () => {
590         formHelper.setMultipleValues(ecp, true);
591         testCreation();
592       });
593
594       it('should send profile with all required fields and crush root and locality', () => {
595         ecpChange('l', '6');
596         formHelper.setMultipleValues(ecp, true);
597         formHelper.setValue('crushRoot', component.buckets[2], true);
598         submittedEcp['crush-root'] = 'mix-host';
599         formHelper.setValue('crushLocality', 'osd-rack', true);
600         submittedEcp['crush-locality'] = 'osd-rack';
601         testCreation();
602       });
603
604       it('should not send the profile with unsupported fields', () => {
605         formHelper.setMultipleValues(ecp, true);
606         formHelper.setValue('c', 4, true);
607         testCreation();
608       });
609     });
610
611     describe(`'shec' usage`, () => {
612       beforeEach(() => {
613         ecpChange('name', 'shecProfile');
614         ecpChange('plugin', 'shec');
615         submittedEcp.k = 4;
616         submittedEcp.m = 3;
617         submittedEcp.c = 2;
618         delete submittedEcp.packetsize;
619         delete submittedEcp.technique;
620       });
621
622       it('should be able to create a profile with only plugin and name', () => {
623         formHelper.setMultipleValues(ecp, true);
624         testCreation();
625       });
626
627       it('should send profile with plugin, name, c and crush device class only', () => {
628         ecpChange('c', '3');
629         formHelper.setMultipleValues(ecp, true);
630         formHelper.setValue('crushDeviceClass', 'ssd', true);
631         submittedEcp['crush-device-class'] = 'ssd';
632         testCreation();
633       });
634
635       it('should not send the profile with unsupported fields', () => {
636         formHelper.setMultipleValues(ecp, true);
637         formHelper.setValue('l', 8, true);
638         testCreation();
639       });
640     });
641
642     describe(`'clay' usage`, () => {
643       beforeEach(() => {
644         ecpChange('name', 'clayProfile');
645         ecpChange('plugin', 'clay');
646         // Setting expectations
647         submittedEcp.k = 4;
648         submittedEcp.m = 2;
649         submittedEcp.d = 5;
650         submittedEcp.scalar_mds = 'jerasure';
651         delete submittedEcp.packetsize;
652       });
653
654       it('should be able to create a profile with only plugin and name', () => {
655         formHelper.setMultipleValues(ecp, true);
656         testCreation();
657       });
658
659       it('should send profile with a changed d', () => {
660         formHelper.setMultipleValues(ecp, true);
661         ecpChange('d', '5');
662         submittedEcp.d = 5;
663         testCreation();
664       });
665
666       it('should send profile with a changed k which automatically changes d', () => {
667         ecpChange('k', 5);
668         formHelper.setMultipleValues(ecp, true);
669         submittedEcp.d = 6;
670         testCreation();
671       });
672
673       it('should send profile with a changed sclara_mds', () => {
674         ecpChange('scalar_mds', 'shec');
675         formHelper.setMultipleValues(ecp, true);
676         submittedEcp.scalar_mds = 'shec';
677         submittedEcp.technique = 'single';
678         testCreation();
679       });
680
681       it('should not send the profile with unsupported fields', () => {
682         formHelper.setMultipleValues(ecp, true);
683         formHelper.setValue('l', 8, true);
684         testCreation();
685       });
686     });
687   });
688 });