testability-driver 1.1.1 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/config/sut_parameters.rb +21 -8
- data/config/tdriver_custom_error_recovery.rb +83 -0
- data/ext/extconf.rb +3 -2
- data/ext/native_extensions.c +60 -2
- data/lib/tdriver-devtools/behaviour/old/xml/example/flick-example.rb +2 -105
- data/lib/tdriver/base/behaviour/factory.rb +154 -89
- data/lib/tdriver/base/behaviour/factory_new.rb +409 -0
- data/lib/tdriver/base/errors.rb +3 -3
- data/lib/tdriver/base/state_object.rb +85 -22
- data/lib/tdriver/base/sut/adapter.rb +26 -0
- data/lib/tdriver/base/sut/controller.rb +1 -1
- data/lib/tdriver/base/sut/generic/behaviours/application.rb +89 -118
- data/lib/tdriver/base/sut/generic/behaviours/find.rb +67 -62
- data/lib/tdriver/base/sut/generic/behaviours/sut.rb +296 -187
- data/lib/tdriver/base/sut/generic/behaviours/switchbox_behaviour.rb +7 -7
- data/lib/tdriver/base/sut/generic/commands/application.rb +366 -295
- data/lib/tdriver/base/sut/sut.rb +19 -3
- data/lib/tdriver/base/test_object/abstract.rb +41 -21
- data/lib/tdriver/base/test_object/adapter.rb +62 -9
- data/lib/tdriver/base/test_object/behaviours/syncronization.rb +10 -6
- data/lib/tdriver/base/test_object/behaviours/test_object.rb +84 -47
- data/lib/tdriver/base/test_object/factory.rb +124 -68
- data/lib/tdriver/base/test_object/loader.rb +3 -4
- data/lib/tdriver/base/test_object/verification.rb +3 -3
- data/lib/tdriver/base/test_object/xml/adapter.rb +734 -0
- data/lib/tdriver/loader.rb +12 -0
- data/lib/tdriver/report/error_recovery/tdriver_error_recovery.rb +3 -2
- data/lib/tdriver/report/error_recovery/tdriver_error_recovery_settings.rb +14 -14
- data/lib/tdriver/report/report.rb +4 -8
- data/lib/tdriver/report/report_api.rb +9 -0
- data/lib/tdriver/report/report_crash_file_capture.rb +4 -4
- data/lib/tdriver/report/report_creator.rb +57 -35
- data/lib/tdriver/report/report_cucumber.rb +1 -1
- data/lib/tdriver/report/report_cucumber_listener.rb +5 -158
- data/lib/tdriver/report/report_cucumber_reporter.rb +7 -161
- data/lib/tdriver/report/report_execution_statistics.rb +4 -4
- data/lib/tdriver/report/report_file_capture.rb +5 -5
- data/lib/tdriver/report/report_grouping.rb +24 -22
- data/lib/tdriver/report/report_junit_xml.rb +5 -5
- data/lib/tdriver/report/report_test_case_run.rb +31 -22
- data/lib/tdriver/report/report_test_run.rb +107 -104
- data/lib/tdriver/report/report_writer.rb +150 -83
- data/lib/tdriver/tdriver.rb +147 -103
- data/lib/tdriver/util/common/boolean.rb +51 -0
- data/lib/tdriver/util/common/crc16.rb +110 -68
- data/lib/tdriver/util/common/hash.rb +63 -7
- data/lib/tdriver/util/common/kernel.rb +46 -1
- data/lib/tdriver/util/common/loader.rb +1 -0
- data/lib/tdriver/util/common/object.rb +20 -8
- data/lib/tdriver/util/common/string.rb +21 -2
- data/lib/tdriver/util/logger/logger.rb +4 -4
- data/lib/tdriver/util/parameter/loader.rb +2 -19
- data/lib/tdriver/util/parameter/parameter.rb +874 -177
- data/lib/tdriver/util/plugin/service.rb +1 -1
- data/lib/tdriver/util/recorder/recorder.rb +7 -1
- data/lib/tdriver/util/xml/abstraction.rb +13 -1
- data/lib/tdriver/util/xml/parsers/nokogiri/abstraction.rb +63 -10
- data/lib/tdriver/util/xml/parsers/nokogiri/attribute.rb +8 -2
- data/lib/tdriver/util/xml/parsers/nokogiri/document.rb +16 -3
- data/lib/tdriver/util/xml/parsers/nokogiri/node.rb +36 -32
- data/lib/tdriver/util/xml/parsers/nokogiri/nodeset.rb +19 -22
- data/lib/tdriver/util/xml/xml.rb +147 -32
- data/lib/tdriver/verify/verify.rb +1112 -289
- data/lib/tdriver/version.rb +1 -1
- data/xml/templates/generic.xml +14 -2
- metadata +51 -24
- data/lib/tdriver/util/parameter/parameter_hash.rb +0 -104
- data/lib/tdriver/util/parameter/parameter_new.rb +0 -869
- data/lib/tdriver/util/parameter/parameter_template.rb +0 -120
- data/lib/tdriver/util/parameter/parameter_user_api.rb +0 -116
- data/lib/tdriver/util/parameter/parameter_xml.rb +0 -261
@@ -138,7 +138,7 @@ module MobyBehaviour
|
|
138
138
|
def freeze
|
139
139
|
|
140
140
|
=begin
|
141
|
-
if
|
141
|
+
if sut_parameters[ :use_find_object, 'false' ] == 'true' && self.respond_to?( 'find_object' )
|
142
142
|
|
143
143
|
warn("warning: SUT##{ __method__ } is not supported when use_find_objects optimization is enabled")
|
144
144
|
|
@@ -166,7 +166,7 @@ module MobyBehaviour
|
|
166
166
|
def unfreeze
|
167
167
|
|
168
168
|
=begin
|
169
|
-
if
|
169
|
+
if sut_parameters[ :use_find_object, 'false' ] == 'true' && self.respond_to?( 'find_object' )
|
170
170
|
|
171
171
|
warn("warning: SUT##{ __method__ } is not supported when use_find_objects optimization is enabled")
|
172
172
|
|
@@ -280,7 +280,7 @@ module MobyBehaviour
|
|
280
280
|
) if identification_directives.has_key?( :__logging )
|
281
281
|
|
282
282
|
# disable logging if requested, remove pair from creation_hash
|
283
|
-
$logger.push_enabled( identification_directives[ :__logging ] ||
|
283
|
+
$logger.push_enabled( identification_directives[ :__logging ] || $logger.enabled )
|
284
284
|
|
285
285
|
begin
|
286
286
|
|
@@ -299,19 +299,19 @@ module MobyBehaviour
|
|
299
299
|
|
300
300
|
rescue MobyBase::MultipleTestObjectsIdentifiedError => exception
|
301
301
|
|
302
|
-
|
302
|
+
$logger.behaviour "FAIL;Multiple child objects matched criteria.;#{ id };sut;{};child;#{ attributes.inspect }"
|
303
303
|
|
304
304
|
Kernel::raise exception
|
305
305
|
|
306
306
|
rescue MobyBase::TestObjectNotFoundError => exception
|
307
307
|
|
308
|
-
|
308
|
+
$logger.behaviour "FAIL;The child object could not be found.;#{ id };sut;{};child;#{ attributes.inspect }"
|
309
309
|
|
310
310
|
Kernel::raise exception
|
311
311
|
|
312
312
|
rescue Exception => exception
|
313
313
|
|
314
|
-
|
314
|
+
$logger.behaviour "FAIL;Failed when trying to find child object.;#{ id };sut;{};child;#{ attributes.inspect }"
|
315
315
|
|
316
316
|
Kernel::raise exception
|
317
317
|
|
@@ -330,6 +330,7 @@ module MobyBehaviour
|
|
330
330
|
|
331
331
|
# == description
|
332
332
|
# Method for executing sut specific setup method
|
333
|
+
# https://projects.forum.nokia.com/Testabilitydriver/wiki/FeatureSutSetupTeardown
|
333
334
|
# == returns
|
334
335
|
# Result
|
335
336
|
# description: -
|
@@ -344,15 +345,15 @@ module MobyBehaviour
|
|
344
345
|
if self.parameter[ :sut_setup, nil ]
|
345
346
|
require MobyUtil::FileHelper.expand_path(self.parameter[ :sut_setup ])
|
346
347
|
|
347
|
-
|
348
|
+
$logger.behaviour "PASS;sut.setup method found"
|
348
349
|
|
349
350
|
self.setup
|
350
351
|
|
351
|
-
|
352
|
+
$logger.behaviour "PASS;sut.setup executed"
|
352
353
|
end
|
353
354
|
|
354
355
|
if self.parameter[ :setup, nil ]
|
355
|
-
|
356
|
+
$logger.behaviour "PASS;sut.setup parameters found"
|
356
357
|
methods=self.parameter[ :setup ]
|
357
358
|
methods.each do |method|
|
358
359
|
m=method[0].to_s
|
@@ -363,11 +364,11 @@ module MobyBehaviour
|
|
363
364
|
eval("self.#{m}(:#{args.to_sym})")
|
364
365
|
end
|
365
366
|
end
|
366
|
-
|
367
|
+
$logger.behaviour "PASS;sut.setup parameter methods executed"
|
367
368
|
end
|
368
369
|
|
369
370
|
else
|
370
|
-
|
371
|
+
$logger.behaviour "FAIL;No methods or parameters found for sut.setup"
|
371
372
|
|
372
373
|
Kernel::raise MobyBase::BehaviourError.new("Setup", "Failed to load sut.setup method check the :sut_setup parameter")
|
373
374
|
end
|
@@ -378,6 +379,7 @@ module MobyBehaviour
|
|
378
379
|
|
379
380
|
# == description
|
380
381
|
# Method for executing sut specific teardown method
|
382
|
+
# https://projects.forum.nokia.com/Testabilitydriver/wiki/FeatureSutSetupTeardown
|
381
383
|
# == returns
|
382
384
|
# Result
|
383
385
|
# description: -
|
@@ -392,15 +394,15 @@ module MobyBehaviour
|
|
392
394
|
if self.parameter[ :sut_teardown, nil ]
|
393
395
|
require MobyUtil::FileHelper.expand_path(self.parameter[ :sut_teardown ])
|
394
396
|
|
395
|
-
|
397
|
+
$logger.behaviour "PASS;sut.teardown method found"
|
396
398
|
|
397
399
|
self.teardown
|
398
400
|
|
399
|
-
|
401
|
+
$logger.behaviour "PASS;sut.teardown executed"
|
400
402
|
end
|
401
403
|
|
402
404
|
if self.parameter[ :teardown, nil ]
|
403
|
-
|
405
|
+
$logger.behaviour "PASS;sut.teardown parameters found"
|
404
406
|
methods=self.parameter[ :teardown ]
|
405
407
|
methods.each do |method|
|
406
408
|
m=method[0].to_s
|
@@ -411,11 +413,11 @@ module MobyBehaviour
|
|
411
413
|
eval("self.#{m}(:#{args.to_sym})")
|
412
414
|
end
|
413
415
|
end
|
414
|
-
|
416
|
+
$logger.behaviour "PASS;sut.teardown parameter methods executed"
|
415
417
|
end
|
416
418
|
|
417
419
|
else
|
418
|
-
|
420
|
+
$logger.behaviour "FAIL;No method or parameters found for sut.teardown"
|
419
421
|
|
420
422
|
Kernel::raise MobyBase::BehaviourError.new("Teardown", "Failed to load sut.teardown method check the :sut_teardown parameter")
|
421
423
|
end
|
@@ -423,23 +425,65 @@ module MobyBehaviour
|
|
423
425
|
end
|
424
426
|
|
425
427
|
# == description
|
426
|
-
#
|
427
|
-
#
|
428
|
+
# Creates a state object of current test object or given XML as argument. The state object is static and thus is not refreshed or synchronized.
|
429
|
+
#
|
430
|
+
# == arguments
|
431
|
+
# source_data
|
432
|
+
# String
|
433
|
+
# description: Object state as XML string
|
434
|
+
# example: -
|
435
|
+
# MobyBase::XML::Element
|
436
|
+
# description: Object state as XML element
|
437
|
+
# example: -
|
438
|
+
#
|
439
|
+
# parent_object
|
440
|
+
# MobyBase::TestObject
|
441
|
+
# description: Parent object
|
442
|
+
# example: -
|
443
|
+
# MobyBase::SUT
|
444
|
+
# description: Parent object
|
445
|
+
# example: -
|
446
|
+
# NilClass
|
447
|
+
# description: No parent object defined
|
448
|
+
# example: nil
|
449
|
+
#
|
428
450
|
# == returns
|
429
|
-
# StateObject
|
430
|
-
# description: State of this test object
|
451
|
+
# MobyBase::StateObject
|
452
|
+
# description: State of this SUT, test object or given XML
|
431
453
|
# example: -
|
454
|
+
#
|
432
455
|
# == exceptions
|
456
|
+
# ArgumentError
|
457
|
+
# description: Wrong argmument type given
|
433
458
|
# RuntimeError
|
434
459
|
# description: If the XML source for the object is not in initialized
|
435
|
-
def
|
460
|
+
def state_object( source_data = nil, parent_object = nil )
|
461
|
+
|
462
|
+
if source_data.nil?
|
436
463
|
|
437
|
-
|
438
|
-
|
464
|
+
# refresh if xml data is empty
|
465
|
+
self.refresh if @xml_data.empty?
|
439
466
|
|
440
|
-
|
467
|
+
Kernel::raise RuntimeError, "Can not create state object of SUT with id #{ @id.inspect }, no XML content or SUT not initialized properly." if @xml_data.empty?
|
441
468
|
|
442
|
-
|
469
|
+
source_data = @test_object_adapter.state_object_xml( @xml_data, @id )
|
470
|
+
|
471
|
+
parent_object = self
|
472
|
+
|
473
|
+
end
|
474
|
+
|
475
|
+
# verify that type of xml_source argument is correct
|
476
|
+
source_data.check_type [ String, MobyUtil::XML::Element ], 'wrong argument type $1 for state object source data (expected $2)'
|
477
|
+
|
478
|
+
parent_object.check_type [ MobyBase::SUT, MobyBase::TestObject, MobyBase::StateObject, NilClass ], 'wrong argument type $1 for parent object (expected $2)'
|
479
|
+
|
480
|
+
MobyBase::StateObject.new(
|
481
|
+
|
482
|
+
:source_data => source_data,
|
483
|
+
:parent => parent_object,
|
484
|
+
:test_object_adapter => @test_object_adapter
|
485
|
+
|
486
|
+
)
|
443
487
|
|
444
488
|
end
|
445
489
|
|
@@ -465,7 +509,7 @@ module MobyBehaviour
|
|
465
509
|
|
466
510
|
begin
|
467
511
|
|
468
|
-
attributes.check_type( Hash,
|
512
|
+
attributes.check_type( Hash, 'Wrong argument type $1 for attributes (expected $2)' )
|
469
513
|
|
470
514
|
attributes[ :type ] = 'application'
|
471
515
|
|
@@ -483,7 +527,7 @@ module MobyBehaviour
|
|
483
527
|
|
484
528
|
rescue
|
485
529
|
|
486
|
-
|
530
|
+
$logger.behaviour(
|
487
531
|
"FAIL;Failed to find application.;#{ id.to_s };sut;{};application;#{ attributes.kind_of?( Hash ) ? attributes.inspect : attributes.class.to_s }"
|
488
532
|
)
|
489
533
|
|
@@ -492,7 +536,7 @@ module MobyBehaviour
|
|
492
536
|
|
493
537
|
ensure
|
494
538
|
|
495
|
-
|
539
|
+
$logger.behaviour "PASS;Application found.;#{ id.to_s };sut;{};application;#{ attributes.inspect }" if $!.nil?
|
496
540
|
|
497
541
|
end
|
498
542
|
|
@@ -565,13 +609,13 @@ module MobyBehaviour
|
|
565
609
|
|
566
610
|
rescue
|
567
611
|
|
568
|
-
|
612
|
+
$logger.behaviour "FAIL;Failed to capture screen.;#{ id.to_s };sut;{};capture_screen;#{ arguments.kind_of?( Hash ) ? arguments.inspect : arguments.class.to_s }"
|
569
613
|
|
570
614
|
Kernel::raise $!
|
571
615
|
|
572
616
|
end
|
573
617
|
|
574
|
-
|
618
|
+
$logger.behaviour "PASS;Screen was captured successfully.;#{ id.to_s };sut;{};capture_screen;#{ arguments.inspect }"
|
575
619
|
|
576
620
|
nil
|
577
621
|
|
@@ -584,8 +628,11 @@ module MobyBehaviour
|
|
584
628
|
# == arguments
|
585
629
|
# target
|
586
630
|
# Hash
|
587
|
-
# description:
|
588
|
-
# example: { :name =>
|
631
|
+
# description: Used to indetify the application to be executed. All symbols defined in the hash must match with the launched application. See application [link="#run_hash_arguments"]run argument hash keys[/link] table.
|
632
|
+
# example: { :name => "calculator" }
|
633
|
+
# String
|
634
|
+
# description: If target application is given in String format it is interpreted as application name. String "calculator"' is equivalent to {:name => "calculator"} hash.
|
635
|
+
# example: "calculator"
|
589
636
|
#
|
590
637
|
# == tables
|
591
638
|
# run_hash_arguments
|
@@ -594,13 +641,15 @@ module MobyBehaviour
|
|
594
641
|
# |Key|Type|Description|Example|
|
595
642
|
# |:uid|String or Integer|Unique ID of the application|{ :uid => 268458181 }|
|
596
643
|
# |:name|String|Executable name of the application|{ :name => 'calculator' }|
|
644
|
+
# |:restart_if_running|Boolean|Restart application if already running||{ :restart_if_running => true }|
|
597
645
|
# |:arguments|String|Comma separated list of arguments passed to the application when it is started|{ :arguments => '--nogui,-v' }|
|
598
|
-
#
|
599
|
-
#
|
600
|
-
#
|
601
|
-
#
|
602
|
-
#
|
603
|
-
#
|
646
|
+
# |:check_pid|Boolean|Overrides default value of SUT parameter :application_check_pid; When set to true, process id is used to test object identification|false|
|
647
|
+
# |:sleep_time|Integer|Number of seconds to sleep immediately after launching the process|{ :sleep_time => 10 }|
|
648
|
+
# |:start_command|String|When set, the run method will execute this command and expect the application provided by the :name key to be launched. Note that applications launched this way can't be sent a Kill message and its start up events and signals may not be recorded.|{ :start_command => 'start_app_batch',:name => 'calculator' }|
|
649
|
+
# |:try_attach|Boolean|If set to true, run will attempt to attach to an existing application with the given name or id. If not found the application will be launched as normal. If more than 1 are found then an exception is thrown|{:try_attach => true, :name => 'calculator'}|
|
650
|
+
# |:environment|String|Environment variables you want to pass to started process, passed as key value pairs separated by '=' and pairs separated by spaces |{ :environment => 'LC_ALL=en SPECIAL_VAR=value' }|
|
651
|
+
# |:events_to_listen|String|List of events you want to start listening to when application starts, passed as comma separated string. You can retrieve a list of events fired by a test object by first enabling event listening and then using the get_events method. See methods enable_events, get_events and disable_events |{ :events_to_listen => 'Paint,Show' }|
|
652
|
+
# |:signals_to_listen|String|List of signals you want to start listening to when application starts, passed as comma separated string. Check your application class what signals it can emit, or you can use the 'signal' fixture's 'list_signal' method to retrieve an xml string listing all the signals the object can emit. E.g. xml = @object.fixture('signal', 'list_signals')|{ :signals_to_listen => 'applicationReady()' }|
|
604
653
|
#
|
605
654
|
# == returns
|
606
655
|
# TestObject
|
@@ -622,27 +671,51 @@ module MobyBehaviour
|
|
622
671
|
begin
|
623
672
|
|
624
673
|
# set the refresh interval to zero while the application is launched
|
625
|
-
#orig_interval =
|
626
|
-
|
674
|
+
#orig_interval = sut_parameters[ :refresh_interval ]
|
675
|
+
#sut_parameters[ :refresh_interval ] = '0'
|
627
676
|
|
628
677
|
# raise exception if argument type other than hash
|
629
|
-
target.check_type( Hash, "Wrong argument type $1 for run method (expected $2)" )
|
678
|
+
target.check_type( [ String, Hash ], "Wrong argument type $1 for run method (expected $2)" )
|
679
|
+
|
680
|
+
# if target application is given as string, interpret it as application name
|
681
|
+
target = { :name => target.to_s } if target.kind_of?( String )
|
630
682
|
|
631
683
|
# default value for missing keys
|
632
684
|
target.default = nil
|
633
685
|
|
634
686
|
# raise exception if :uid or :name not found from hash
|
635
|
-
target.
|
687
|
+
target.require_one( [ :uid, :name ], "Required key :uid or :name not found from argument hash" )
|
636
688
|
|
637
689
|
# due to bug #1488
|
638
690
|
sleep_time = ( target[ :sleep_after_launch ] || target[ :sleep_time ] ).to_i
|
639
691
|
|
692
|
+
timeout_time = sut_parameters[ :application_synchronization_timeout, '5' ].to_f
|
693
|
+
|
694
|
+
retry_interval = sut_parameters[ :application_synchronization_retry_interval, '0.5' ].to_f
|
695
|
+
|
696
|
+
if target.has_key?( :check_pid )
|
697
|
+
|
698
|
+
check_pid = target[ :check_pid ].check_type [ TrueClass, FalseClass ], 'wrong argument type $1 for SUT#run :check_pid (expected $2)'
|
699
|
+
|
700
|
+
else
|
701
|
+
|
702
|
+
# due to bug #1710; pid checking must be configurable
|
703
|
+
check_pid = sut_parameters[ :application_check_pid, false ].to_s.to_boolean( false )
|
704
|
+
|
705
|
+
end
|
706
|
+
|
640
707
|
Kernel::raise ArgumentError, "Sleep time need to be >= 0" unless sleep_time >= 0
|
641
708
|
|
642
709
|
# try to find an existing app with the current arguments
|
643
|
-
if target[ :try_attach ]
|
710
|
+
if target[ :try_attach ] || target[:restart_if_running]
|
711
|
+
|
712
|
+
app_list = MobyBase::StateObject.new(
|
713
|
+
|
714
|
+
:source_data => self.list_apps,
|
715
|
+
:parent => nil,
|
716
|
+
:test_object_adapter => @test_object_adapter
|
644
717
|
|
645
|
-
|
718
|
+
)
|
646
719
|
|
647
720
|
# either ID or NAME have been passed to identify the application
|
648
721
|
# raise exception if more than one app has been found for this id/name
|
@@ -653,15 +726,20 @@ module MobyBehaviour
|
|
653
726
|
|
654
727
|
app = self.application(:id => app_info.id) if app_info
|
655
728
|
|
656
|
-
if app
|
729
|
+
if target[:restart_if_running] && app
|
730
|
+
|
731
|
+
# Close the application,
|
732
|
+
app.close # (:force_kill => true)
|
657
733
|
|
734
|
+
elsif app
|
735
|
+
|
658
736
|
begin
|
659
737
|
|
660
738
|
app.bring_to_foreground
|
661
739
|
|
662
740
|
rescue Exception => e
|
663
741
|
|
664
|
-
|
742
|
+
$logger.warning "Could not bring app to foreground"
|
665
743
|
|
666
744
|
end
|
667
745
|
|
@@ -679,27 +757,11 @@ module MobyBehaviour
|
|
679
757
|
|
680
758
|
else
|
681
759
|
|
682
|
-
=begin
|
683
760
|
# execute the application control service request
|
684
|
-
|
685
|
-
|
686
|
-
MobyCommand::Application.new(
|
687
|
-
:Run,
|
688
|
-
target[ :name ],
|
689
|
-
target[ :uid ],
|
690
|
-
self,
|
691
|
-
target[ :arguments ],
|
692
|
-
target[ :environment ],
|
693
|
-
target[ :working_directory ],
|
694
|
-
target[ :events_to_listen ],
|
695
|
-
target[ :signals_to_listen ]
|
696
|
-
)
|
697
|
-
|
698
|
-
)
|
699
|
-
=end
|
761
|
+
# the run request will return the pid if all goes well
|
762
|
+
app_pid = nil
|
700
763
|
|
701
|
-
|
702
|
-
execute_command(
|
764
|
+
app_pid = execute_command(
|
703
765
|
MobyCommand::Application.new(
|
704
766
|
:Run,
|
705
767
|
{
|
@@ -719,37 +781,39 @@ module MobyBehaviour
|
|
719
781
|
|
720
782
|
# do not remove this, unless qttas server & plugin handles the syncronization between plugin registration & first ui state request
|
721
783
|
# first ui dump is requested too early and target/server seems not be ready...
|
722
|
-
#sleep 0.100
|
723
|
-
|
724
784
|
sleep sleep_time if sleep_time > 0
|
725
|
-
|
785
|
+
|
786
|
+
# Now the application id is its PID that we get from the execute_command response
|
726
787
|
expected_attributes = { :type => 'application' }
|
727
788
|
|
728
|
-
|
789
|
+
# fix to bug #1710; pid checking must be configurable
|
790
|
+
if check_pid == true
|
791
|
+
|
792
|
+
expected_attributes[ :id ] = app_pid unless app_pid.nil?
|
729
793
|
|
794
|
+
end
|
795
|
+
|
730
796
|
expected_attributes[ :FullName ] = target[ :name ] unless target[ :name ].nil?
|
731
797
|
|
798
|
+
# For error reporting
|
732
799
|
error_details = target[ :name ].nil? ? "" : "name: " << target[ :name ].to_s
|
733
800
|
error_details << ( error_details.empty? ? "" : ", ") << "id: " << target[ :uid ].to_s if !target[ :uid ].nil?
|
734
801
|
|
735
|
-
|
802
|
+
# Calculate the application name from :FullName ( used later )
|
803
|
+
app_name = target[ :name ].nil? ? "" : "name: " << target[ :name ].to_s
|
736
804
|
|
737
805
|
if( !expected_attributes[ :FullName ].nil? )
|
738
|
-
|
739
806
|
if( expected_attributes[ :FullName ].include?('/') )
|
740
|
-
|
741
807
|
app_name = expected_attributes[ :FullName ].split('/')[ expected_attributes[ :FullName ].split( '/' ).size-1 ]
|
742
808
|
app_name.slice!( ".exe" )
|
743
809
|
expected_attributes[ :name ] = app_name
|
744
810
|
|
745
811
|
elsif( expected_attributes[ :FullName ].include?("\\") )
|
746
|
-
|
747
812
|
app_name = expected_attributes[ :FullName ].split("\\")[ expected_attributes[ :FullName ].split( "\\" ).size-1 ]
|
748
813
|
app_name.slice!( ".exe" )
|
749
814
|
expected_attributes[:name] = app_name
|
750
815
|
|
751
816
|
else
|
752
|
-
|
753
817
|
app_name = expected_attributes[ :FullName ]
|
754
818
|
app_name.slice!( ".exe" )
|
755
819
|
expected_attributes[ :name ] = app_name
|
@@ -758,17 +822,16 @@ module MobyBehaviour
|
|
758
822
|
|
759
823
|
expected_attributes.delete( :FullName )
|
760
824
|
expected_attributes.delete( :name )
|
761
|
-
|
762
825
|
end
|
763
826
|
|
827
|
+
# Wait for application to register and then create the application test object
|
764
828
|
begin
|
765
|
-
timeout_time=$parameters[ @id ][ :application_synchronization_timeout, '5' ].to_f
|
766
|
-
retryinterval=$parameters[ @id ][ :application_synchronization_retry_interval, '0.5' ].to_f
|
767
829
|
|
768
830
|
MobyUtil::Retryable.until(
|
769
831
|
:timeout => timeout_time,
|
770
|
-
:interval =>
|
832
|
+
:interval => retry_interval,
|
771
833
|
:exception => MobyBase::ApplicationNotAvailableError) {
|
834
|
+
|
772
835
|
# verify that application is launched and application test object is found from xml
|
773
836
|
expected_attributes.delete( :name )
|
774
837
|
|
@@ -781,17 +844,17 @@ module MobyBehaviour
|
|
781
844
|
timeout_time,
|
782
845
|
|
783
846
|
# wait retry interval and try again if application was not found
|
784
|
-
|
847
|
+
retry_interval
|
785
848
|
|
786
849
|
)
|
787
850
|
|
788
851
|
expected_attributes[ :name ] = app_name
|
789
852
|
# retrieve application object element from sut.xml_data
|
790
853
|
|
791
|
-
|
854
|
+
@matches, unused_rule = @test_object_adapter.get_objects( xml_data, expected_attributes, true )
|
792
855
|
|
793
856
|
# raise exception if application element was not found; this shouldn't ever happen?
|
794
|
-
raise MobyBase::ApplicationNotAvailableError if
|
857
|
+
raise MobyBase::ApplicationNotAvailableError if @matches.count == 0
|
795
858
|
|
796
859
|
}
|
797
860
|
|
@@ -804,7 +867,7 @@ module MobyBehaviour
|
|
804
867
|
|
805
868
|
:object_attributes_hash => expected_attributes,
|
806
869
|
|
807
|
-
:xml_object =>
|
870
|
+
:xml_object => @matches.first
|
808
871
|
|
809
872
|
)
|
810
873
|
|
@@ -824,16 +887,16 @@ module MobyBehaviour
|
|
824
887
|
|
825
888
|
end
|
826
889
|
|
827
|
-
|
828
|
-
rescue
|
890
|
+
# raise behaviour error if any exception is raised
|
891
|
+
rescue
|
829
892
|
|
830
|
-
|
893
|
+
$logger.behaviour "FAIL;Failed to launch application.;#{ id.to_s };sut;{};run;#{ target.kind_of?( Hash ) ? target.inspect : target.class.to_s }"
|
831
894
|
|
832
895
|
Kernel::raise MobyBase::BehaviourError.new("Run", "Failed to launch application")
|
833
896
|
|
834
897
|
end
|
835
898
|
|
836
|
-
|
899
|
+
$logger.behaviour "PASS;The application was launched successfully.;#{ id.to_s };sut;{};run;#{ target.inspect }"
|
837
900
|
|
838
901
|
foreground_app
|
839
902
|
|
@@ -887,7 +950,15 @@ module MobyBehaviour
|
|
887
950
|
|
888
951
|
value.check_type( [ Symbol, MobyCommand::KeySequence ], "Wrong argument type $1 for press_key (expected $2)" )
|
889
952
|
|
890
|
-
|
953
|
+
if value.kind_of?( Symbol )
|
954
|
+
|
955
|
+
sequence = MobyCommand::KeySequence.new( value )
|
956
|
+
|
957
|
+
else
|
958
|
+
|
959
|
+
sequence = value
|
960
|
+
|
961
|
+
end
|
891
962
|
|
892
963
|
sequence.set_sut( self )
|
893
964
|
|
@@ -895,13 +966,13 @@ module MobyBehaviour
|
|
895
966
|
|
896
967
|
rescue
|
897
968
|
|
898
|
-
|
969
|
+
$logger.behaviour "FAIL;Failed to press key(s).;#{id.to_s};sut;{};press_key;#{ value }"
|
899
970
|
|
900
971
|
Kernel::raise $!
|
901
972
|
|
902
973
|
end
|
903
974
|
|
904
|
-
|
975
|
+
$logger.behaviour "PASS;Successfully pressed key(s).;#{ id.to_s };sut;{};press_key;#{ value }"
|
905
976
|
|
906
977
|
nil
|
907
978
|
|
@@ -925,7 +996,7 @@ module MobyBehaviour
|
|
925
996
|
# description: Value matching the parameter name given as argument
|
926
997
|
# example: 'testability-driver-qt-sut-plugin'
|
927
998
|
#
|
928
|
-
#
|
999
|
+
# TDriver::ParameterHash
|
929
1000
|
# description: Hash of values, if no arguments is specified
|
930
1001
|
# example: { :value => '1', :inner_hash => { :another_value => 100 } }
|
931
1002
|
#
|
@@ -943,13 +1014,11 @@ module MobyBehaviour
|
|
943
1014
|
|
944
1015
|
if ( arguments.count == 0 )
|
945
1016
|
|
946
|
-
|
1017
|
+
$parameters[ @id ]
|
947
1018
|
|
948
1019
|
else
|
949
1020
|
|
950
|
-
|
951
|
-
|
952
|
-
MobyUtil::ParameterUserAPI[ @id ][ *arguments ]
|
1021
|
+
$parameters[ @id ][ *arguments ]
|
953
1022
|
|
954
1023
|
end
|
955
1024
|
|
@@ -985,7 +1054,7 @@ module MobyBehaviour
|
|
985
1054
|
# example: "1"
|
986
1055
|
# default: nil
|
987
1056
|
# Integer
|
988
|
-
# description: Optional numeral replacement of an '%Ln | %1' tag on the translated string
|
1057
|
+
# description: Optional numeral replacement of an '%Ln | %1 | %D | %U | %N' tag on the translated string
|
989
1058
|
# example: 1
|
990
1059
|
# Array
|
991
1060
|
# description: Optional numeral replacements for multiple '%L1 | %1, %L2 | %2, ...' tags on the translated string
|
@@ -1055,9 +1124,9 @@ module MobyBehaviour
|
|
1055
1124
|
|
1056
1125
|
when "localisation"
|
1057
1126
|
|
1058
|
-
language=nil
|
1127
|
+
language = nil
|
1059
1128
|
|
1060
|
-
if (
|
1129
|
+
if ( sut_parameters[ :read_lang_from_app ]=='true')
|
1061
1130
|
|
1062
1131
|
#read localeName app
|
1063
1132
|
language=self.application.attribute("localeName")
|
@@ -1067,7 +1136,7 @@ module MobyBehaviour
|
|
1067
1136
|
|
1068
1137
|
else
|
1069
1138
|
|
1070
|
-
language
|
1139
|
+
language = sut_parameters[ :language ]
|
1071
1140
|
|
1072
1141
|
end
|
1073
1142
|
|
@@ -1076,7 +1145,7 @@ module MobyBehaviour
|
|
1076
1145
|
translation = MobyUtil::Localisation.translation(
|
1077
1146
|
logical_name,
|
1078
1147
|
language,
|
1079
|
-
|
1148
|
+
sut_parameters[ :localisation_server_database_tablename ],
|
1080
1149
|
file_name,
|
1081
1150
|
plurality,
|
1082
1151
|
lengthvariant
|
@@ -1090,7 +1159,7 @@ module MobyBehaviour
|
|
1090
1159
|
|
1091
1160
|
elsif numerus.kind_of? String or numerus.kind_of? Integer
|
1092
1161
|
|
1093
|
-
translation.gsub!(/%(Ln|1)/){|s| numerus.to_s}
|
1162
|
+
translation.gsub!(/%(Ln|1|U|D|N)/){|s| numerus.to_s}
|
1094
1163
|
|
1095
1164
|
end
|
1096
1165
|
|
@@ -1104,7 +1173,7 @@ module MobyBehaviour
|
|
1104
1173
|
|
1105
1174
|
elsif numerus.kind_of? String or numerus.kind_of? Integer
|
1106
1175
|
|
1107
|
-
trans.gsub!(/%(Ln|1)/){|s| numerus.to_s}
|
1176
|
+
trans.gsub!(/%(Ln|1|U|D|N)/){|s| numerus.to_s}
|
1108
1177
|
|
1109
1178
|
end
|
1110
1179
|
|
@@ -1191,10 +1260,10 @@ module MobyBehaviour
|
|
1191
1260
|
user_data_lname,
|
1192
1261
|
|
1193
1262
|
# language
|
1194
|
-
|
1263
|
+
sut_parameters[ :language ],
|
1195
1264
|
|
1196
1265
|
# table name
|
1197
|
-
|
1266
|
+
sut_parameters[ :user_data_server_database_tablename ]
|
1198
1267
|
|
1199
1268
|
)
|
1200
1269
|
|
@@ -1234,10 +1303,10 @@ module MobyBehaviour
|
|
1234
1303
|
operator_data_lname,
|
1235
1304
|
|
1236
1305
|
# operator
|
1237
|
-
|
1306
|
+
sut_parameters[ :operator_selected ],
|
1238
1307
|
|
1239
1308
|
# table name
|
1240
|
-
|
1309
|
+
sut_parameters[ :operator_data_server_database_tablename ]
|
1241
1310
|
|
1242
1311
|
)
|
1243
1312
|
|
@@ -1252,9 +1321,7 @@ module MobyBehaviour
|
|
1252
1321
|
# === raises
|
1253
1322
|
def update
|
1254
1323
|
|
1255
|
-
|
1256
|
-
|
1257
|
-
unless @childs_updated
|
1324
|
+
if @update_childs
|
1258
1325
|
|
1259
1326
|
@child_object_cache.each_object{ | test_object |
|
1260
1327
|
|
@@ -1264,9 +1331,17 @@ module MobyBehaviour
|
|
1264
1331
|
|
1265
1332
|
}
|
1266
1333
|
|
1267
|
-
|
1334
|
+
@update_childs = false
|
1268
1335
|
|
1269
|
-
|
1336
|
+
# childs were updated
|
1337
|
+
true
|
1338
|
+
|
1339
|
+
else
|
1340
|
+
|
1341
|
+
# nothing was updated
|
1342
|
+
false
|
1343
|
+
|
1344
|
+
end
|
1270
1345
|
|
1271
1346
|
end
|
1272
1347
|
|
@@ -1275,7 +1350,7 @@ module MobyBehaviour
|
|
1275
1350
|
|
1276
1351
|
refresh_ui_dump( refresh_args, creation_attributes )
|
1277
1352
|
|
1278
|
-
# update childs
|
1353
|
+
# update childs if required, returns true or false
|
1279
1354
|
update_childs
|
1280
1355
|
|
1281
1356
|
end
|
@@ -1340,13 +1415,13 @@ module MobyBehaviour
|
|
1340
1415
|
def get_application_id
|
1341
1416
|
|
1342
1417
|
# retrieve application object from sut.xml_data
|
1343
|
-
matches, unused_rule =
|
1418
|
+
matches, unused_rule = @test_object_adapter.get_objects( xml_data, { :type => 'application' }, true )
|
1344
1419
|
|
1345
1420
|
# retrieve id attribute if application test object found
|
1346
1421
|
if matches.count > 0
|
1347
1422
|
|
1348
1423
|
# return id attribute value
|
1349
|
-
|
1424
|
+
@test_object_adapter.test_object_element_attribute( matches.first, 'id' )
|
1350
1425
|
|
1351
1426
|
else
|
1352
1427
|
|
@@ -1357,13 +1432,13 @@ module MobyBehaviour
|
|
1357
1432
|
|
1358
1433
|
end
|
1359
1434
|
|
1360
|
-
|
1435
|
+
private
|
1361
1436
|
|
1362
1437
|
# TODO: document me
|
1363
1438
|
def update_childs
|
1364
1439
|
|
1365
1440
|
# update childs only if ui state is new
|
1366
|
-
update if
|
1441
|
+
update if @update_childs
|
1367
1442
|
|
1368
1443
|
end
|
1369
1444
|
|
@@ -1377,72 +1452,77 @@ module MobyBehaviour
|
|
1377
1452
|
|
1378
1453
|
if !@frozen #&& ( @_previous_refresh.nil? || ( current_time - @_previous_refresh ).to_f >= @refresh_interval )
|
1379
1454
|
|
1380
|
-
|
1455
|
+
# determine should FindObjects service be used
|
1456
|
+
use_find_objects = sut_parameters[ :use_find_object, 'false' ] == 'true' and self.respond_to?( 'find_object' ) == true
|
1381
1457
|
|
1458
|
+
# duplicate refresh arguments hash
|
1382
1459
|
refresh_arguments = refresh_args.clone
|
1383
1460
|
|
1384
1461
|
MobyUtil::Retryable.while(
|
1385
1462
|
:tries => @refresh_tries,
|
1386
1463
|
:interval => @refresh_interval,
|
1387
1464
|
:unless => [ MobyBase::ControllerNotFoundError, MobyBase::CommandNotFoundError, MobyBase::ApplicationNotAvailableError ]
|
1388
|
-
)
|
1465
|
+
){
|
1389
1466
|
|
1390
1467
|
#use find_object if set on and the method exists
|
1391
1468
|
if use_find_objects
|
1392
1469
|
|
1393
|
-
|
1470
|
+
# retrieve new ui dump xml and crc
|
1471
|
+
new_xml_data, crc = find_object( refresh_arguments, creation_attributes, @xml_data_crc )
|
1472
|
+
|
1473
|
+
crc = @xml_data_crc if new_xml_data.empty?
|
1394
1474
|
|
1395
1475
|
else
|
1396
1476
|
|
1397
|
-
|
1398
|
-
|
1399
|
-
|
1400
|
-
|
1401
|
-
|
1402
|
-
|
1477
|
+
# retrieve new ui dump xml and crc
|
1478
|
+
new_xml_data, crc = execute_command(
|
1479
|
+
|
1480
|
+
MobyCommand::Application.new(
|
1481
|
+
:State,
|
1482
|
+
{
|
1483
|
+
:application_name => refresh_args[ :FullName ] || refresh_args[ :name ],
|
1484
|
+
:application_uid => refresh_args[ :id ],
|
1485
|
+
:sut => self,
|
1486
|
+
:refresh_arguments => refresh_args,
|
1487
|
+
:checksum => @xml_data_crc
|
1488
|
+
}
|
1489
|
+
)
|
1490
|
+
|
1403
1491
|
)
|
1404
|
-
=end
|
1405
1492
|
|
1406
|
-
|
1407
|
-
:State,
|
1408
|
-
{
|
1409
|
-
:application_name => refresh_args[ :FullName ] || refresh_args[ :name ],
|
1410
|
-
:application_uid => refresh_args[ :id ],
|
1411
|
-
:sut => self
|
1412
|
-
}
|
1413
|
-
)
|
1493
|
+
crc = @xml_data_crc if new_xml_data.empty?
|
1414
1494
|
|
1415
|
-
|
1416
|
-
app_command.refresh_args( refresh_args )
|
1495
|
+
end
|
1417
1496
|
|
1418
|
-
|
1497
|
+
# parse the xml if crc does not match with previously retrieved crc
|
1498
|
+
if ( @xml_data_crc == 0 || crc != @xml_data_crc || crc.nil? )
|
1419
1499
|
|
1420
|
-
|
1500
|
+
# parse new xml string, return cached object if one is found; crc is used for caching and identifying the duplicate xml strings
|
1501
|
+
xml_data, from_cache = MobyUtil::XML.parse_string( new_xml_data, crc )
|
1421
1502
|
|
1422
|
-
|
1503
|
+
# store new xml data object
|
1504
|
+
@xml_data = xml_data.root
|
1423
1505
|
|
1424
|
-
|
1506
|
+
# store xml crc to be compared while next ui dump request; do not reparse xml if crc values are equal
|
1507
|
+
@xml_data_crc = crc
|
1425
1508
|
|
1426
|
-
|
1509
|
+
# mark that child objects needs to be updated
|
1510
|
+
@update_childs = true #unless from_cache
|
1427
1511
|
|
1428
|
-
|
1512
|
+
end
|
1429
1513
|
|
1430
|
-
#
|
1431
|
-
|
1432
|
-
# @xml_data = MobyUtil::XML.parse_string( new_xml_data ).root
|
1433
|
-
# @last_xml_data = xml_data_no_timestamp
|
1434
|
-
#end
|
1514
|
+
# increase number of sent ui dump requests by one
|
1515
|
+
@dump_count += 1
|
1435
1516
|
|
1436
|
-
#
|
1437
|
-
|
1438
|
-
#end
|
1517
|
+
# store timestamp of last performed ui dump request
|
1518
|
+
@_previous_refresh = Time.now
|
1439
1519
|
|
1440
1520
|
}
|
1441
1521
|
|
1442
1522
|
end
|
1443
1523
|
|
1444
|
-
# fetch_references( @xml_data )
|
1445
1524
|
@xml_data
|
1525
|
+
|
1446
1526
|
end
|
1447
1527
|
|
1448
1528
|
# TODO: document me
|
@@ -1604,58 +1684,72 @@ module MobyBehaviour
|
|
1604
1684
|
|
1605
1685
|
end
|
1606
1686
|
|
1607
|
-
def
|
1608
|
-
|
1609
|
-
@xml_data = ""
|
1610
|
-
|
1611
|
-
@x_path = '.'
|
1612
|
-
|
1613
|
-
@frozen = false
|
1614
|
-
|
1615
|
-
@child_object_cache = TDriver::TestObjectCache.new
|
1616
|
-
|
1617
|
-
@current_application_id = nil
|
1618
|
-
|
1619
|
-
@dump_count = 0
|
1620
|
-
|
1621
|
-
# default values
|
1622
|
-
@input = :key
|
1623
|
-
|
1624
|
-
@refresh_tries = 5
|
1625
|
-
@refresh_interval = 0.5
|
1687
|
+
def load_verify_blocks( filename )
|
1626
1688
|
|
1627
|
-
|
1689
|
+
# load verify blocks if filename not empty
|
1690
|
+
unless filename.blank?
|
1628
1691
|
|
1629
|
-
|
1630
|
-
|
1692
|
+
# verify that file exists
|
1693
|
+
if File.exists?( filename )
|
1631
1694
|
|
1632
|
-
|
1695
|
+
# load verify blocks configuration file
|
1696
|
+
load filename
|
1633
1697
|
|
1634
|
-
|
1698
|
+
# return collection of verify blocks; reference directly to VERIFY_BLOCKS must not be used, due to it may get cleared by user
|
1699
|
+
SutParameters::VERIFY_BLOCKS.collect{ | block | block }
|
1635
1700
|
|
1636
|
-
|
1701
|
+
else
|
1702
|
+
|
1703
|
+
# return empty array due to file didn't exist
|
1704
|
+
[]
|
1705
|
+
|
1706
|
+
end
|
1637
1707
|
|
1708
|
+
else
|
1709
|
+
|
1710
|
+
# return empty array due to no filename was given
|
1711
|
+
[]
|
1712
|
+
|
1638
1713
|
end
|
1639
1714
|
|
1640
|
-
|
1641
|
-
|
1642
|
-
ruby_file = $parameters[ @id ][ :verify_blocks ]
|
1715
|
+
end
|
1643
1716
|
|
1644
|
-
|
1717
|
+
# TODO: document me
|
1718
|
+
def initialize_settings
|
1645
1719
|
|
1646
|
-
|
1720
|
+
# default values
|
1721
|
+
@x_path = '.'
|
1722
|
+
@xml_data = ""
|
1723
|
+
@dump_count = 0
|
1647
1724
|
|
1648
|
-
|
1725
|
+
# determines that should child test objects be updated
|
1726
|
+
@update_childs = true
|
1727
|
+
|
1728
|
+
@last_xml_data = nil
|
1729
|
+
@frozen = false
|
1649
1730
|
|
1650
|
-
|
1731
|
+
# initialize cache for sut children
|
1732
|
+
@child_object_cache = TDriver::TestObjectCache.new
|
1651
1733
|
|
1652
|
-
|
1734
|
+
@current_application_id = nil
|
1653
1735
|
|
1654
|
-
|
1736
|
+
# create empty hash for sut parameters if sut id not found from parameters
|
1737
|
+
$parameters[ @id ] = {} unless $parameters.has_key?( @id )
|
1655
1738
|
|
1739
|
+
@input = sut_parameters[ :input_type, 'key' ].to_sym
|
1740
|
+
@refresh_tries = sut_parameters[ :ui_state_refresh_tries, '5' ].to_f
|
1741
|
+
@refresh_interval = sut_parameters[ :refresh_interval, '0.5' ].to_f
|
1656
1742
|
|
1657
|
-
|
1743
|
+
# load verify blocks from defined sut configuration file
|
1744
|
+
@verify_blocks = load_verify_blocks( sut_parameters[ :verify_blocks, nil ] )
|
1745
|
+
|
1746
|
+
end
|
1658
1747
|
|
1748
|
+
# accessor for sut parameters
|
1749
|
+
def sut_parameters
|
1750
|
+
|
1751
|
+
$parameters[ @id ]
|
1752
|
+
|
1659
1753
|
end
|
1660
1754
|
|
1661
1755
|
public # deprecated
|
@@ -1669,7 +1763,7 @@ module MobyBehaviour
|
|
1669
1763
|
|
1670
1764
|
warn "warning: deprecated method SUT#get_object; please use SUT#child instead"
|
1671
1765
|
|
1672
|
-
child object_id
|
1766
|
+
child( object_id )
|
1673
1767
|
|
1674
1768
|
end
|
1675
1769
|
|
@@ -1686,7 +1780,22 @@ module MobyBehaviour
|
|
1686
1780
|
|
1687
1781
|
#$stderr.puts "warning: SUT#get_ui_dump is deprecated, please use SUT#refresh_ui_dump instead."
|
1688
1782
|
|
1689
|
-
refresh_ui_dump refresh_args, {}
|
1783
|
+
refresh_ui_dump( refresh_args, {} )
|
1784
|
+
|
1785
|
+
end
|
1786
|
+
|
1787
|
+
# This method is deprecated, please use [link="#GenericSut:state_object"]SUT#state_object[/link] instead.
|
1788
|
+
# == deprecated
|
1789
|
+
# 1.1.1
|
1790
|
+
#
|
1791
|
+
# == description
|
1792
|
+
# This method is deprecated, please use SUT#state_object
|
1793
|
+
#
|
1794
|
+
def state
|
1795
|
+
|
1796
|
+
warn "warning: deprecated method SUT#state; please use SUT#state_object instead"
|
1797
|
+
|
1798
|
+
state_object
|
1690
1799
|
|
1691
1800
|
end
|
1692
1801
|
|