testability-driver 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. data/lib/tdriver-devtools/behaviour/xml/rdoc_behaviour_xml_generator.rb +2 -2
  2. data/lib/tdriver-devtools/tdriver-devtools.rb +1 -1
  3. data/lib/tdriver-devtools/tests/feature_tests/lib/custom_rdoc_generator.rb +3 -3
  4. data/lib/tdriver/base/behaviour/behaviours/object_behaviour_composition.rb +6 -1
  5. data/lib/tdriver/base/behaviour/behaviours/object_behaviour_description.rb +5 -3
  6. data/lib/tdriver/base/behaviour/behaviours/object_composition.rb +1 -1
  7. data/lib/tdriver/base/behaviour/factory.rb +225 -225
  8. data/lib/tdriver/base/errors.rb +1 -1
  9. data/lib/tdriver/base/state_object.rb +227 -179
  10. data/lib/tdriver/base/sut/controller.rb +2 -2
  11. data/lib/tdriver/base/sut/factory.rb +190 -182
  12. data/lib/tdriver/base/sut/generic/behaviours/application.rb +69 -25
  13. data/lib/tdriver/base/sut/generic/behaviours/controller.rb +1 -1
  14. data/lib/tdriver/base/sut/generic/behaviours/find.rb +4 -4
  15. data/lib/tdriver/base/sut/generic/behaviours/flash_behaviour.rb +3 -3
  16. data/lib/tdriver/base/sut/generic/behaviours/sut.rb +350 -165
  17. data/lib/tdriver/base/sut/generic/behaviours/switchbox_behaviour.rb +9 -9
  18. data/lib/tdriver/base/sut/generic/behaviours/verification.rb +191 -103
  19. data/lib/tdriver/base/sut/generic/commands/application.rb +1 -1
  20. data/lib/tdriver/base/sut/generic/commands/key_sequence.rb +1 -1
  21. data/lib/tdriver/base/sut/generic/commands/screen_capture.rb +1 -1
  22. data/lib/tdriver/base/sut/generic/plugin.rb +1 -1
  23. data/lib/tdriver/base/sut/sut.rb +5 -1
  24. data/lib/tdriver/base/test_object/abstract.rb +136 -151
  25. data/lib/tdriver/base/test_object/adapter.rb +293 -82
  26. data/lib/tdriver/base/test_object/behaviours/syncronization.rb +20 -17
  27. data/lib/tdriver/base/test_object/behaviours/test_object.rb +159 -532
  28. data/lib/tdriver/base/test_object/cache.rb +1 -1
  29. data/lib/tdriver/base/test_object/factory.rb +254 -605
  30. data/lib/tdriver/base/test_object/identificator.rb +1 -1
  31. data/lib/tdriver/base/test_object/loader.rb +1 -1
  32. data/lib/tdriver/base/test_object/verification.rb +17 -17
  33. data/lib/tdriver/loader.rb +20 -9
  34. data/lib/tdriver/report/report.rb +5 -0
  35. data/lib/tdriver/report/report_creator.rb +2 -2
  36. data/lib/tdriver/report/report_cucumber_listener.rb +4 -4
  37. data/lib/tdriver/report/report_cucumber_reporter.rb +4 -4
  38. data/lib/tdriver/report/report_execution_statistics.rb +22 -22
  39. data/lib/tdriver/report/report_grouping.rb +2 -2
  40. data/lib/tdriver/report/report_javascript.rb +11 -4
  41. data/lib/tdriver/report/report_test_case_run.rb +2 -2
  42. data/lib/tdriver/report/report_test_run.rb +5 -5
  43. data/lib/tdriver/report/report_test_unit.rb +74 -26
  44. data/lib/tdriver/report/report_writer.rb +70 -13
  45. data/lib/tdriver/tdriver.rb +17 -8
  46. data/lib/tdriver/util/common/array.rb +1 -1
  47. data/lib/tdriver/util/common/crc16.rb +1 -1
  48. data/lib/tdriver/util/common/environment.rb +1 -1
  49. data/lib/tdriver/util/common/file.rb +18 -9
  50. data/lib/tdriver/util/common/gem.rb +1 -1
  51. data/lib/tdriver/util/common/hash.rb +21 -0
  52. data/lib/tdriver/util/common/kernel.rb +1 -1
  53. data/lib/tdriver/util/common/loader.rb +5 -2
  54. data/lib/tdriver/util/common/numeric.rb +54 -3
  55. data/lib/tdriver/util/common/retryable.rb +30 -12
  56. data/lib/tdriver/util/common/stackable.rb +185 -0
  57. data/lib/tdriver/util/common/string.rb +21 -5
  58. data/lib/tdriver/util/{dbaccess/dbaccess.rb → database/access.rb} +4 -1
  59. data/lib/tdriver/util/{dbaccess/dbconnection.rb → database/connection.rb} +3 -0
  60. data/lib/tdriver/util/{dbaccess → database}/error.rb +0 -1
  61. data/lib/tdriver/util/{dbaccess → database}/loader.rb +5 -6
  62. data/lib/tdriver/util/{dynamic_attribute_filter.rb → filters/dynamic_attributes.rb} +1 -1
  63. data/lib/tdriver/util/hooking/hooking.rb +477 -0
  64. data/lib/tdriver/util/loader.rb +35 -29
  65. data/lib/tdriver/util/localisation/error.rb +0 -1
  66. data/lib/tdriver/util/localisation/loader.rb +1 -4
  67. data/lib/tdriver/util/localisation/localisation.rb +30 -27
  68. data/lib/tdriver/util/{common.rb → logger/loader.rb} +2 -4
  69. data/lib/tdriver/util/logger/logger.rb +574 -0
  70. data/lib/tdriver/util/operator_data/loader.rb +4 -3
  71. data/lib/tdriver/util/operator_data/operator_data.rb +5 -5
  72. data/lib/tdriver/util/parameter/parameter.rb +7 -1
  73. data/lib/tdriver/util/parameter/parameter_hash.rb +1 -1
  74. data/lib/tdriver/util/parameter/parameter_template.rb +1 -1
  75. data/lib/tdriver/util/parameter/parameter_user_api.rb +28 -20
  76. data/lib/tdriver/util/parameter/parameter_xml.rb +1 -1
  77. data/lib/tdriver/util/plugin/abstract.rb +1 -1
  78. data/lib/tdriver/util/plugin/service.rb +1 -1
  79. data/lib/tdriver/util/{localisation.rb → recorder/loader.rb} +4 -3
  80. data/lib/tdriver/util/recorder/recorder.rb +66 -0
  81. data/lib/tdriver/util/recorder/scripter.rb +258 -0
  82. data/lib/tdriver/util/{stats.rb → statistics/statistics.rb} +7 -8
  83. data/lib/tdriver/util/user_data/error.rb +0 -1
  84. data/lib/tdriver/util/user_data/loader.rb +1 -2
  85. data/lib/tdriver/util/user_data/user_data.rb +6 -6
  86. data/lib/tdriver/util/video/camera.rb +67 -0
  87. data/lib/tdriver/util/video/camera_linux.rb +139 -0
  88. data/lib/tdriver/util/video/camera_windows.rb +174 -0
  89. data/lib/tdriver/util/video/loader.rb +31 -0
  90. data/lib/tdriver/util/video/video_utils.rb +139 -0
  91. data/lib/tdriver/util/xml/abstraction.rb +56 -5
  92. data/lib/tdriver/util/xml/builder.rb +2 -5
  93. data/lib/tdriver/util/{parameter.rb → xml/comment.rb} +10 -2
  94. data/lib/tdriver/util/xml/loader.rb +32 -22
  95. data/lib/tdriver/util/xml/nil_node.rb +2 -2
  96. data/lib/tdriver/util/xml/parsers/loader.rb +0 -1
  97. data/lib/tdriver/util/xml/parsers/nokogiri/abstraction.rb +18 -44
  98. data/lib/tdriver/util/xml/parsers/nokogiri/attribute.rb +9 -13
  99. data/lib/tdriver/util/xml/parsers/nokogiri/builder.rb +9 -3
  100. data/lib/tdriver/util/xml/parsers/nokogiri/comment.rb +39 -0
  101. data/lib/tdriver/util/xml/parsers/nokogiri/document.rb +6 -11
  102. data/lib/tdriver/util/xml/parsers/nokogiri/element.rb +2 -122
  103. data/lib/tdriver/util/xml/parsers/nokogiri/loader.rb +26 -16
  104. data/lib/tdriver/util/xml/parsers/nokogiri/node.rb +203 -0
  105. data/lib/tdriver/util/xml/parsers/nokogiri/nodeset.rb +1 -2
  106. data/lib/tdriver/util/xml/parsers/nokogiri/text.rb +2 -20
  107. data/lib/tdriver/util/xml/xml.rb +52 -20
  108. data/lib/tdriver/verify/verify.rb +238 -81
  109. data/xml/behaviours/generic.xml +12 -10
  110. metadata +156 -180
  111. data/lib/tdriver/base/test_object/factory_new.rb +0 -202
  112. data/lib/tdriver/util/hooking.rb +0 -434
  113. data/lib/tdriver/util/logger.rb +0 -506
  114. data/lib/tdriver/util/recorder.rb +0 -297
  115. data/lib/tdriver/util/video_utils.rb +0 -384
  116. data/lib/tdriver/util/xml/nil_element.rb +0 -89
@@ -1,506 +0,0 @@
1
- ############################################################################
2
- ##
3
- ## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4
- ## All rights reserved.
5
- ## Contact: Nokia Corporation (testabilitydriver@nokia.com)
6
- ##
7
- ## This file is part of Testability Driver.
8
- ##
9
- ## If you have questions regarding the use of this file, please contact
10
- ## Nokia at testabilitydriver@nokia.com .
11
- ##
12
- ## This library is free software; you can redistribute it and/or
13
- ## modify it under the terms of the GNU Lesser General Public
14
- ## License version 2.1 as published by the Free Software Foundation
15
- ## and appearing in the file LICENSE.LGPL included in the packaging
16
- ## of this file.
17
- ##
18
- ############################################################################
19
-
20
- require 'log4r'
21
- require 'log4r/configurator'
22
-
23
- module MobyUtil
24
-
25
- class Logger
26
-
27
- include Singleton
28
-
29
- attr_reader :include_behaviour_info
30
-
31
- def initialize()
32
-
33
- # Allow all levels to be reported - do not change this!
34
- @custom_levels = ['debug', 'behaviour', 'info', 'warning', 'error', 'fatal']
35
-
36
- Log4r::Configurator.custom_levels( *@custom_levels.collect{ | level | level.upcase } )
37
-
38
- Log4r::Logger.root.level = Log4r::DEBUG
39
-
40
- @enabled_stack = [ false ]
41
-
42
- @logger_instance = nil
43
-
44
- end
45
-
46
- def include_behaviour_info=( value )
47
-
48
- raise ArgumentError.new( "Unexpected variable type (%s) for 'include_behaviour_info' boolean, expected TrueClass or FalseClass" ) unless [ TrueClass, FalseClass ].include?( value.class )
49
-
50
- @include_behaviour_info = value
51
-
52
- end
53
-
54
- # allow reporting by passing level as method name, raise exception if method_id not found in @custom_levels array
55
- def method_missing( method_id, *method_arguments )
56
-
57
- super unless @custom_levels.include?( method_id.to_s )
58
-
59
- self.log( method_id.to_s, *method_arguments )
60
-
61
- end
62
-
63
- # TODO: add documentation
64
- def enabled
65
-
66
- @enabled_stack[ -1 ]
67
-
68
- end
69
-
70
- # TODO: add documentation
71
- def enabled=( value )
72
-
73
- @enabled_stack[ -1 ] = value
74
-
75
- end
76
-
77
- # TODO: add documentation
78
- def push_enabled( value )
79
-
80
- # push current value to stack if given argument is other than boolean
81
- value = @enabled_stack[ -1 ] unless [ TrueClass, FalseClass ].include?( value.class )
82
-
83
- @enabled_stack << value
84
-
85
- end
86
-
87
- # TODO: add documentation
88
- def pop_enabled
89
-
90
- @enabled_stack.pop if @enabled_stack.count > 1
91
-
92
- end
93
-
94
- # TODO: add documentation
95
- def set_report_level( report_level )
96
-
97
- Log4r::Logger.root.level = report_level
98
-
99
- end
100
-
101
- # TODO: add documentation
102
- def new_logger( logger_name )
103
-
104
- Log4r::Logger.new( logger_name )
105
-
106
- end
107
-
108
- # TODO: add documentation
109
- def get_logger( logger_name )
110
-
111
- begin
112
-
113
- Log4r::Logger.get( logger_name )
114
-
115
- rescue
116
-
117
- Kernel::raise ArgumentError.new( "Logger '%s' not found" % logger_name )
118
-
119
- end
120
-
121
- end
122
-
123
- # TODO: add documentation
124
- def create_outputter( outputter_class, *args )
125
-
126
- outputter_class.new( *args )
127
-
128
- end
129
-
130
- # TODO: add documentation
131
- def add_outputter( logger_instance, outputter_instance )
132
-
133
- logger_instance.add( outputter_instance )
134
-
135
- end
136
-
137
- # TODO: add documentation
138
- def remove_outputter( logger_instance, outputter_instance )
139
-
140
- logger_instance.remove( outputter_instance )
141
-
142
- end
143
-
144
- # TODO: add documentation
145
- def set_outputter_pattern( outputter_instance, pattern )
146
-
147
- # Allow only FileOutputter instances
148
- Kernel::raise ArgumentError.new("Outputter instance not valid") if ![ Log4r::FileOutputter ].include?( outputter_instance.class )
149
-
150
- # Allow only FileOutputter instances
151
- Kernel::raise ArgumentError.new("Outputter pattern not valid, %M required by minimum") if !/\%M/.match( pattern )
152
-
153
- # create pattern for outputter
154
- outputter_instance.formatter = Log4r::PatternFormatter.new( :pattern => pattern )
155
-
156
- end
157
-
158
- # TODO: add documentation
159
- # return logger instance
160
- def self.[]( key )
161
-
162
- self.instance.get_logger( key )
163
-
164
- end
165
-
166
- # TODO: add documentation
167
- def root
168
-
169
- Log4r::Logger.global
170
-
171
- end
172
-
173
- # TODO: add documentation
174
- def log( level, *text_array )
175
-
176
- if self.enabled && @logger_instance
177
-
178
- # convert to lowercase string
179
- level = level.to_s.downcase
180
-
181
- include_behaviour_info = @include_behaviour_info
182
-
183
- # debug log entries and logging by using TDriver.logging.info or MobyUtil::Logging.instance.info etc
184
- if caller.first =~ /method_missing/
185
-
186
- # get correct caller method
187
- log_caller = caller.at( 1 )
188
-
189
- # debug level
190
- if log_caller =~ /hooking\.rb/
191
-
192
- log_caller = caller.at( 3 ).first
193
-
194
- end
195
-
196
- elsif caller.first =~ /logger\.rb/
197
-
198
- # do not add caller info if called from self
199
- include_behaviour_info = false
200
-
201
- else
202
-
203
- # normal logging, e.g. behaviour logging from method etc
204
- log_caller = caller.at( 0 )
205
-
206
- end
207
-
208
- # log text to given level if logging enabled
209
- text_array.each{ | text |
210
-
211
- @logger_instance.send( level, ( include_behaviour_info && !text.empty? ) ? ( "%s in %s" % [ text, log_caller ] ) : ( "%s" % text ) )
212
-
213
- }
214
-
215
- end
216
-
217
- end
218
-
219
- def enable_raise_hooking
220
-
221
- # hook Kernel.raise
222
- def Kernel::raise( *exception )
223
-
224
- begin
225
-
226
- super( *exception )
227
-
228
- rescue => raised_exception
229
-
230
- raised_exception.backtrace.slice!( 0 )
231
-
232
- warn_array = [ '', "(%s) %s" % [ raised_exception.class, raised_exception.message.split("\n") ], '', raised_exception.backtrace, '' ].flatten
233
-
234
- MobyUtil::Logger.instance.log( 'warning', *warn_array )
235
-
236
- super( raised_exception )
237
-
238
- end
239
-
240
- end
241
-
242
- end
243
-
244
- def set_debug_exceptions
245
-
246
- if ARGV.include?( '--debug_exceptions' ) || TDriver.parameter[ :debug_exceptions, 'false' ].to_s.downcase == 'true'
247
-
248
- ARGV.delete('--debug_exceptions')
249
- # for debugging to see every occured exception
250
- def Kernel.raise( *args )
251
- #begin
252
- # raise and catch exception
253
- super( *args )
254
- #rescue
255
- # remove wrapper call from backtrace
256
- # $!.backtrace.shift
257
- #puts "%s: %s\nBacktrace: \n%s\n\n" % [ $!.class, $!.message, $!.backtrace.collect{ | line | " %s" % line }.join("\n") ]
258
- # raise exception again
259
- # super $!
260
- #end
261
- end
262
-
263
- # hook Object(Kernel)#raise
264
- ::Object.class_exec{
265
-
266
- ::Kernel.module_exec{
267
-
268
- alias_method :original_raise, :raise
269
-
270
- def raise( *args )
271
-
272
- begin
273
-
274
- # raise and catch exception
275
- original_raise( *args )
276
-
277
- rescue
278
-
279
- # remove wrapper calls from backtrace
280
- while $!.backtrace.first =~ /(logger\.rb).*(raise)/
281
-
282
- $!.backtrace.shift
283
-
284
- end
285
-
286
- puts "[debug] %s: %s\n[debug] Backtrace: \n[debug] %s\n\n" % [
287
- $!.class,
288
- $!.message,
289
- $!.backtrace.collect{ | line | " ... from %s" % line }.join("\n[debug] ")
290
- ]
291
-
292
- # raise exception again
293
- original_raise $!
294
-
295
- end
296
-
297
- end
298
-
299
- }
300
- }
301
-
302
- end
303
-
304
- end
305
-
306
- # TODO: add documentation
307
- def enable_logging
308
-
309
- set_debug_exceptions # if enabled
310
-
311
- # returns logging level as string
312
- logging_level = Parameter[ :logging_level, nil ]
313
-
314
- # do not enable logging if no logging level is not defined
315
- return nil if logging_level.nil?
316
-
317
- # raise exception if wrong format for logging level
318
- Kernel::raise RuntimeError.new(
319
-
320
- "Wrong logging level format '%s' defined in TDriver parameter/template XML (expected %s)" % [ logging_level, "numeric string"]
321
-
322
- ) unless logging_level.numeric? #MobyUtil::StringHelper.numeric?( logging_level )
323
-
324
- # convert to integer
325
- logging_level = logging_level.to_i
326
-
327
- # raise exception if unsupported logging level
328
- Kernel::raise RuntimeError.new(
329
-
330
- "Unsupported logging level '%s' defined in TDriver parameter/template XML (expected %s)" % [ logging_level, "0..5"]
331
-
332
- ) unless (0..5).include?( logging_level )
333
-
334
- @include_behaviour_info = ( MobyUtil::Parameter[ :logging_include_behaviour_info, 'false' ].downcase == 'true' )
335
-
336
- # UI state XML parse error logging - verify that all required parameters are configured and output folder is created succesfully
337
- if MobyUtil::KernelHelper.to_boolean( MobyUtil::Parameter[ :logging_xml_parse_error_dump, 'false' ] ) == true
338
-
339
- begin
340
-
341
- if MobyUtil::Parameter[ :logging_xml_parse_error_dump_path, nil ].nil?
342
-
343
- warn("Warning: Configuration parameter :logging_xml_parse_error_dump_path missing, disabling the feature...")
344
-
345
- # disable feature
346
- raise ArgumentError
347
-
348
- else
349
-
350
- begin
351
-
352
- # create error dump folder if not exist, used e.g. when xml parse error
353
- MobyUtil::FileHelper.mkdir_path( MobyUtil::FileHelper.expand_path( $last_parameter ) )
354
-
355
- rescue Exception
356
-
357
- warn("Warning: Unable to create log folder %s for corrupted XML UI state files" % [ MobyUtil::Parameter[ :logging_xml_parse_error_dump_path ] ] )
358
-
359
- # disable feature
360
- raise ArgumentError
361
-
362
- end
363
-
364
- end
365
-
366
- if MobyUtil::Parameter[ :logging_xml_parse_error_dump_overwrite, nil ].nil?
367
-
368
- warn("Warning: Configuration parameter :logging_xml_parse_error_dump_overwrite missing, using 'false' as default value")
369
-
370
- MobyUtil::Parameter[ :logging_xml_parse_error_dump_overwrite ] = 'false'
371
-
372
- end
373
-
374
- rescue ArgumentError => exception
375
-
376
- MobyUtil::Parameter[ :logging_xml_parse_error_dump ] = 'false'
377
-
378
- rescue Exception => exception
379
-
380
- warn("Warning: Disabling logging due to failure (%s: %s)" % [ exception.class, exception.message ] )
381
-
382
- MobyUtil::Parameter[ :logging_xml_parse_error_dump ] = 'false'
383
-
384
- end
385
-
386
- else
387
-
388
- warn("Warning: Configuration parameter :logging_xml_parse_error_dump missing, disabling the feature...")
389
- MobyUtil::Parameter[ :logging_xml_parse_error_dump ] = 'false'
390
-
391
- end
392
-
393
- unless logging_level.zero?
394
-
395
- # create new logger instance
396
- MobyUtil::Logger.instance.new_logger( 'TDriver' )
397
-
398
- # get logger object reference
399
- @logger_instance = get_logger( 'TDriver' )
400
-
401
- # create unique name for logfile or use default (TDriver.log)
402
- filename = ( MobyUtil::StringHelper.to_boolean( Parameter[ :logging_outputter_unique_filename ] ) ? "TDriver_%i.log" % Time.now : "TDriver.log" )
403
-
404
- # logger output path
405
- outputter_path = MobyUtil::FileHelper.expand_path( MobyUtil::Parameter[ :logging_outputter_path ] )
406
-
407
- # create outputter folder if not exist
408
- MobyUtil::FileHelper.mkdir_path( outputter_path )
409
-
410
- # check if outputter is enabled
411
- if MobyUtil::StringHelper.to_boolean( Parameter[ :logging_outputter_enabled ] )
412
-
413
- # create new outputter instance type of FileOutputter
414
- outputter = create_outputter(
415
-
416
- # outputter type
417
- Log4r::FileOutputter,
418
-
419
- # outputter name
420
- "TDriver_LOG",
421
-
422
- # outputter filename
423
- :filename => File.join( outputter_path, filename ),
424
-
425
- # append to or truncate file
426
- :trunc => MobyUtil::StringHelper.to_boolean( Parameter[ :logging_outputter_append ] ) == false,
427
-
428
- # logging level
429
- :level => logging_level
430
-
431
- )
432
-
433
- # set outputter log event write pattern
434
- set_outputter_pattern( outputter, Parameter[ :logging_outputter_pattern ] )
435
-
436
- # add outputter to logger instance
437
- add_outputter( @logger_instance, outputter )
438
-
439
- end
440
-
441
- # debug logging
442
- if ( logging_level == 1 )
443
-
444
- # enable exception capturing on debug level
445
- enable_raise_hooking
446
-
447
- # pass logger instance to hooking module
448
- MobyUtil::Hooking.instance.set_logger_instance( MobyUtil::Logger.instance )
449
-
450
- end
451
-
452
- # enable logging
453
- @enabled_stack = [ true ]
454
-
455
- # log event: start logging
456
- log( 'info' , "", "Logging engine started", "" )
457
-
458
- end
459
-
460
- report_status_at_exit
461
-
462
- end
463
-
464
- def report_status_at_exit
465
-
466
- at_exit{
467
-
468
- begin
469
- exit_status = nil
470
-
471
- case $!
472
-
473
- when NilClass
474
-
475
- exit_status = ['info', '', 'Execution finished succesfully', '']
476
-
477
- when SystemExit
478
-
479
- exit_status = ['info', '', 'Execution terminated by system exit', '' ]
480
-
481
- else
482
-
483
- exit_status = ['error', '', "Execution terminated with exception: %s: %s" % [ caller.first, $!.message.split("\n") ], '' ]
484
-
485
- end
486
-
487
- log( *exit_status )
488
-
489
- rescue
490
-
491
- end
492
-
493
- }
494
- end
495
-
496
- def hook_methods( _base )
497
-
498
- #STDOUT.puts "Use MobyUtil::Hooking instead of MobyUtil::Logging when calling hook_methods (#{ caller(1).first })"
499
-
500
- MobyUtil::Hooking.instance.hook_methods( _base ) #if @enabled
501
-
502
- end
503
-
504
- end # Logger
505
-
506
- end # MobyUtil