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