File: testing.py

package info (click to toggle)
paraview 5.13.2%2Bdfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 544,220 kB
  • sloc: cpp: 3,374,605; ansic: 1,332,409; python: 150,381; xml: 122,166; sql: 65,887; sh: 7,317; javascript: 5,262; yacc: 4,417; java: 3,977; perl: 2,363; lex: 1,929; f90: 1,397; makefile: 170; objc: 153; tcl: 59; pascal: 50; fortran: 29
file content (788 lines) | stat: -rw-r--r-- 30,266 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
r"""
    This module provides some testing functionality for paraview and
    vtk web applications.  It provides the ability to run an arbitrary
    test script in a separate thread and communicate the results back
    to the service so that the CTest framework can be notified of the
    success or failure of the test.

    This test harness will notice when the test script has finished
    running and will notify the service to stop.  At this point, the
    test results will be checked in the main thread which ran the
    service, and in the case of failure an exception will be raised
    to notify CTest of the failure.

    Test scripts need to follow some simple rules in order to work
    within the test harness framework:

    1) implement a function called "runTest(args)", where the args
    parameter contains all the arguments given to the web application
    upon starting.  Among other important items, args will contain the
    port number where the web application is listening.

    2) import the testing module so that the script has access to
    the functions which indicate success and failure.  Also the
    testing module contains convenience functions that might be of
    use to the test scripts.

       from vtk.web import testing

    3) Call the "testPass(testName)" or "testFail(testName)" functions
    from within the runTest(args) function to indicate to the framework
    whether the test passed or failed.

"""

import_warning_info = ""
test_module_comm_queue = None

from vtkmodules.vtkTestingRendering import vtkTesting

# Try standard Python imports
try:
    import os, re, time, datetime, threading, imp, inspect, Queue, types, io
except:
    import_warning_info += "\nUnable to load at least one basic Python module"

# Image comparison imports
try:
    try:
        from PIL import Image
    except ImportError:
        import Image
    except:
        raise
    import base64
    import itertools
except:
    import_warning_info += (
        "\nUnable to load at least one modules necessary for image comparison"
    )

# Browser testing imports
try:
    import selenium
    from selenium import webdriver
except:
    import_warning_info += (
        "\nUnable to load at least one module necessary for browser tests"
    )

# HTTP imports
try:
    import requests
except:
    import_warning_info += (
        "\nUnable to load at least one module necessary for HTTP tests"
    )


# Define some infrastructure to support different (or no) browsers
test_module_browsers = ["firefox", "chrome", "internet_explorer", "safari", "nobrowser"]


class TestModuleBrowsers:
    firefox, chrome, internet_explorer, safari, nobrowser = range(5)


# =============================================================================
# We can use this exception type to indicate that the test shouldn't actually
# "fail", rather that it was unable to run because some dependencies were not
# met.
# =============================================================================
class DependencyError(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


# =============================================================================
# This class allows usage as a dictionary and an object with named property
# access.
# =============================================================================
class Dictionary(dict):
    def __getattribute__(self, attrName):
        return self[attrName]

    def __setattr__(self, attrName, attrValue):
        self[attrName] = attrValue


# =============================================================================
# Checks whether test script supplied, if so, safely imports needed modules
# =============================================================================
def initialize(opts, reactor=None, cleanupMethod=None):
    """
    This function should be called to initialize the testing module.  The first
    important thing it does is to store the options for later, since the
    startTestThread function will need them.  Then it checks the arguments that
    were passed into the server to see if a test was actually requested, making
    a note of this fact.  Then, if a test was required, this function then
    checks if all the necessary testing modules were safely imported, printing
    a warning if not.  If tests were requested and all modules were present,
    then this function sets "test_module_do_testing" to True and sets up the
    startTestThread function to be called after the reactor is running.

        opts: Parsed arguments from the server

        reactor: This argument is optional, but is used by server.py to
        cause the test thread to be started only after the server itself
        has started.  If it is not provided, the test thread is launched
        immediately.

        cleanupMethod: A callback method you would like the test thread
        to execute when the test has finished.  This is used by server.py
        as a way to have the server terminated after the test has finished,
        but could be used for other cleanup purposes.  This argument is
        also optional.
    """

    global import_warning_info

    global testModuleOptions
    testModuleOptions = Dictionary()

    # Copy the testing options into something we can easily extend
    for arg in vars(opts):
        optValue = getattr(opts, arg)
        testModuleOptions[arg] = optValue

    # If we got one, add the cleanup method to the testing options
    if cleanupMethod:
        testModuleOptions["cleanupMethod"] = cleanupMethod

    # Check if a test was actually requested
    if (
        testModuleOptions.testScriptPath != ""
        and testModuleOptions.testScriptPath is not None
    ):
        # Check if we ran into trouble with any of the testing imports
        if import_warning_info != "":
            print("WARNING: Some tests may have unmet dependencies")
            print(import_warning_info)

        if reactor is not None:
            # Add startTest callback to the reactor callback queue, so that
            # the test thread gets started after the reactor is running.  Of
            # course this should only happen if everything is good for tests.
            reactor.callWhenRunning(_start_test_thread)
        else:
            # Otherwise, our aim is to start the thread from another process
            # so just call the start method.
            _start_test_thread()


# =============================================================================
# Grab out the command-line arguments needed for by the testing module.
# =============================================================================
def add_arguments(parser):
    """
    This function retrieves any command-line arguments that the client-side
    tester needs.  In order to run a test, you will typically just need the
    following:

      --run-test-script => This should be the full path to the test script to
      be run.

      --baseline-img-dir => This should be the 'Baseline' directory where the
      baseline images for this test are located.

      --test-use-browser => This should be one of the supported browser types,
      or else 'nobrowser'.  The choices are 'chrome', 'firefox', 'internet_explorer',
      'safari', or 'nobrowser'.
    """

    parser.add_argument(
        "--run-test-script",
        default="",
        help="The path to a test script to run",
        dest="testScriptPath",
    )

    parser.add_argument(
        "--baseline-img-dir",
        default="",
        help="The path to the directory containing the web test baseline images",
        dest="baselineImgDir",
    )

    parser.add_argument(
        "--test-use-browser",
        default="nobrowser",
        help="One of 'chrome', 'firefox', 'internet_explorer', 'safari', or 'nobrowser'.",
        dest="useBrowser",
    )

    parser.add_argument(
        "--temporary-directory",
        default=".",
        help="A temporary directory for storing test images and diffs",
        dest="tmpDirectory",
    )

    parser.add_argument(
        "--test-image-file-name",
        default="",
        help="Name of file in which to store generated test image",
        dest="testImgFile",
    )


# =============================================================================
# Initialize the test client
# =============================================================================
def _start_test_thread():
    """
    This function checks whether testing is required and if so, sets up a Queue
    for the purpose of communicating with the thread.  then it starts the
    after waiting 5 seconds for the server to have a chance to start up.
    """

    global test_module_comm_queue
    test_module_comm_queue = Queue.Queue()

    t = threading.Thread(
        target=launch_web_test,
        args=[],
        kwargs={
            "serverOpts": testModuleOptions,
            "commQueue": test_module_comm_queue,
            "testScript": testModuleOptions.testScriptPath,
        },
    )

    t.start()


# =============================================================================
# Test scripts call this function to indicate passage of their test
# =============================================================================
def test_pass(testName):
    """
    Test scripts should call this function to indicate that the test passed.  A
    note is recorded that the test succeeded, and is checked later on from the
    main thread so that CTest can be notified of this result.
    """

    global test_module_comm_queue
    resultObj = {testName: "pass"}
    test_module_comm_queue.put(resultObj)


# =============================================================================
# Test scripts call this function to indicate failure of their test
# =============================================================================
def test_fail(testName):
    """
    Test scripts should call this function to indicate that the test failed.  A
    note is recorded that the test did not succeed, and this note is checked
    later from the main thread so that CTest can be notified of the result.

    The main thread is the only one that can signal test failure in
    CTest framework, and the main thread won't have a chance to check for
    passage or failure of the test until the main loop has terminated.  So
    here we just record the failure result, then we check this result in the
    processTestResults() function, throwing an exception at that point to
    indicate to CTest that the test failed.
    """

    global test_module_comm_queue
    resultObj = {testName: "fail"}
    test_module_comm_queue.put(resultObj)


# =============================================================================
# Concatenate any number of strings into a single path string.
# =============================================================================
def concat_paths(*pathElts):
    """
    A very simple convenience function so that test scripts can build platform
    independent paths out of a list of elements, without having to import the
    os module.

        pathElts: Any number of strings which should be concatenated together
        in a platform independent manner.
    """

    return os.path.join(*pathElts)


# =============================================================================
# So we can change our time format in a single place, this function is
# provided.
# =============================================================================
def get_current_time_string():
    """
    This function returns the current time as a string, using ISO 8601 format.
    """

    return datetime.datetime.now().isoformat(" ")


# =============================================================================
# Uses vtkTesting to compare images.  According to comments in the vtkTesting
# C++ code (and this seems to work), if there are multiple baseline images in
# the same directory as the baseline_img, and they follow the naming pattern:
# 'img.png', 'img_1.png', ... , 'img_N.png', then all of these images will be
# tried for a match.
# =============================================================================
def compare_images(test_img, baseline_img, tmp_dir="."):
    """
    This function creates a vtkTesting object, and specifies the name of the
    baseline image file, using a fully qualified path (baseline_img must be
    fully qualified).  Then it calls the vtkTesting method which compares the
    image (test_img, specified only with a relative path) against the baseline
    image as well as any other images in the same directory as the baseline
    image which follow the naming pattern: 'img.png', 'img_1.png', ... , 'img_N.png'

        test_img: File name of output image to be compared against baseline.

        baseline_img: Fully qualified path to first of the baseline images.

        tmp_dir: Fully qualified path to a temporary directory for storing images.
    """

    # Create a vtkTesting object and specify a baseline image
    t = vtkTesting()
    t.AddArgument("-T")
    t.AddArgument(tmp_dir)
    t.AddArgument("-V")
    t.AddArgument(baseline_img)

    # Perform the image comparison test and print out the result.
    return t.RegressionTest(test_img, 0.05)


# =============================================================================
# Provide a wait function
# =============================================================================
def wait_with_timeout(delay=None, limit=0, criterion=None):
    """
    This function provides the ability to wait for a certain number of seconds,
    or else to wait for a specific criterion to be met.
    """
    for i in itertools.count():
        if criterion is not None and criterion():
            return True
        elif delay * i > limit:
            return False
        else:
            time.sleep(delay)


# =============================================================================
# Define a WebTest class with five stages of testing: initialization, setup,
# capture, postprocess, and cleanup.
# =============================================================================
class WebTest(object):
    """
    This is the base class for all automated web-based tests.  It defines five
    stages that any test must run through, and allows any or all of these
    stages to be overridden by subclasses.  This class defines the run_test
    method to invoke the five stages overridden by subclasses, one at a time:
    1) initialize, 2) setup, 3) capture, 4) postprocess, and 5) cleanup.
    """

    class Abort:
        pass

    def __init__(self, url=None, testname=None, **kwargs):
        self.url = url
        self.testname = testname

    def run_test(self):
        try:
            self.checkdependencies()
            self.initialize()
            self.setup()
            self.capture()
            self.postprocess()
        except WebTest.Abort:
            # Placeholder for future option to return failure result
            pass
        except:
            self.cleanup()
            raise

        self.cleanup()

    def checkdependencies(self):
        pass

    def initialize(self):
        pass

    def setup(self):
        pass

    def capture(self):
        pass

    def postprocess(self):
        pass

    def cleanup(self):
        pass


# =============================================================================
# Define a WebTest subclass designed specifically for browser-based tests.
# =============================================================================
class BrowserBasedWebTest(WebTest):
    """
    This class can be used as a base for any browser-based web tests.  It
    introduces the notion of a selenium browser and overrides phases (1) and
    (3), initialization and cleanup, of the test phases introduced in the base
    class.  Initialization involves selecting the browser type, setting the
    browser window size, and asking the browser to load the url.  Cleanup
    involves closing the browser window.
    """

    def __init__(self, size=None, browser=None, **kwargs):
        self.size = size
        self.browser = browser
        self.window = None

        WebTest.__init__(self, **kwargs)

    def initialize(self):
        try:
            if self.browser is None or self.browser == TestModuleBrowsers.chrome:
                self.window = webdriver.Chrome()
            elif self.browser == TestModuleBrowsers.firefox:
                self.window = webdriver.Firefox()
            elif self.browser == TestModuleBrowsers.internet_explorer:
                self.window = webdriver.Ie()
            else:
                raise DependencyError(
                    "self.browser argument has illegal value %r" % (self.browser)
                )
        except DependencyError as dErr:
            raise
        except Exception as inst:
            raise DependencyError(inst)

        if self.size is not None:
            self.window.set_window_size(self.size[0], self.size[1])

        if self.url is not None:
            self.window.get(self.url)

    def cleanup(self):
        try:
            self.window.quit()
        except:
            print(
                "Unable to call window.quit, perhaps this is expected because of unmet browser dependency."
            )


# =============================================================================
# Extend BrowserBasedWebTest to handle vtk-style image comparison
# =============================================================================
class ImageComparatorWebTest(BrowserBasedWebTest):
    """
    This class extends browser based web tests to include image comparison.  It
    overrides the capture phase of testing with some functionality to simply
    grab a screenshot of the entire browser window.  It overrides the
    postprocess phase with a call to vtk image comparison functionality.
    Derived classes can then simply override the setup function with a series
    of selenium-based browser interactions to create a complete test.  Derived
    classes may also prefer to override the capture phase to capture only
    certain portions of the browser window for image comparison.
    """

    def __init__(self, filename=None, baseline=None, temporaryDir=None, **kwargs):
        if filename is None:
            raise TypeError("missing argument 'filename'")
        if baseline is None:
            raise TypeError("missing argument 'baseline'")

        BrowserBasedWebTest.__init__(self, **kwargs)
        self.filename = filename
        self.baseline = baseline
        self.tmpDir = temporaryDir

    def capture(self):
        self.window.save_screenshot(self.filename)

    def postprocess(self):
        result = compare_images(self.filename, self.baseline, self.tmpDir)

        if result == 1:
            test_pass(self.testname)
        else:
            test_fail(self.testname)


# =============================================================================
# Given a css selector to use in finding the image element, get the element,
# then base64 decode the "src" attribute and return it.
# =============================================================================
def get_image_data(browser, cssSelector):
    """
    This function takes a selenium browser and a css selector string and uses
    them to find the target HTML image element.  The desired image element
    should contain it's image data as a Base64 encoded JPEG image string.
    The 'src' attribute of the image is read, Base64-decoded, and then
    returned.

        browser: A selenium browser instance, as created by webdriver.Chrome(),
        for example.

        cssSelector: A string containing a CSS selector which will be used to
        find the HTML image element of interest.
    """

    # Here's maybe a better way to get at that image element
    imageElt = browser.find_element_by_css_selector(cssSelector)

    # Now get the Base64 image string and decode it into image data
    base64String = imageElt.get_attribute("src")
    b64RegEx = re.compile(r"data:image/jpeg;base64,(.+)")
    b64Matcher = b64RegEx.match(base64String)
    imgdata = base64.b64decode(b64Matcher.group(1))

    return imgdata


# =============================================================================
# Combines a variation on above function with the write_image_to_disk function.
# converting jpg to png in the process, if necessary.
# =============================================================================
def save_image_data_as_png(browser, cssSelector, imgfilename):
    """
    This function takes a selenium browser instance, a css selector string,
    and a file name.  It uses the css selector string to finds the target HTML
    Image element, which should contain a Base64 encoded JPEG image string,
    it decodes the string to image data, and then saves the data to the file.
    The image type of the written file is determined from the extension of the
    provided filename.

        browser: A selenium browser instance as created by webdriver.Chrome(),
        for example.

        cssSelector: A string containing a CSS selector which will be used to
        find the HTML image element of interest.

        imgFilename: The filename to which to save the image. The extension is
        used to determine the type of image which should be saved.
    """
    imageElt = browser.find_element_by_css_selector(cssSelector)
    base64String = imageElt.get_attribute("src")
    b64RegEx = re.compile(r"data:image/jpeg;base64,(.+)")
    b64Matcher = b64RegEx.match(base64String)
    img = Image.open(io.BytesIO(base64.b64decode(b64Matcher.group(1))))
    img.save(imgfilename)


# =============================================================================
# Given a decoded image and the full path to a file, write the image to the
# file.
# =============================================================================
def write_image_to_disk(imgData, filePath):
    """
    This function takes an image data, as returned by this module's
    get_image_data() function for example, and writes it out to the file given by
    the filePath parameter.

        imgData: An image data object
        filePath: The full path, including the file name and extension, where
        the image should be written.
    """

    with open(filePath, "wb") as f:
        f.write(imgData)


# =============================================================================
# There could be problems if the script file has more than one class defn which
# is a subclass of vtk.web.testing.WebTest, so we should write some
# documentation to help people avoid that.
# =============================================================================
def instantiate_test_subclass(pathToScript, **kwargs):
    """
    This function takes the fully qualified path to a test file, along with
    any needed keyword arguments, then dynamically loads the file as a module
    and finds the test class defined inside of it via inspection.  It then
    uses the keyword arguments to instantiate the test class and return the
    instance.

        pathToScript: Fully qualified path to python file containing defined
        subclass of one of the test base classes.
        kwargs: Keyword arguments to be passed to the constructor of the
        testing subclass.
    """

    # Load the file as a module
    moduleName = imp.load_source("dynamicTestModule", pathToScript)
    instance = None

    # Inspect dynamically loaded module members
    for name, obj in inspect.getmembers(moduleName):
        # Looking for classes only
        if inspect.isclass(obj):
            instance = obj.__new__(obj)
            # And only classes defined in the dynamically loaded module
            if instance.__module__ == "dynamicTestModule":
                try:
                    instance.__init__(**kwargs)
                    break
                except Exception as inst:
                    print("Caught exception: " + str(type(inst)))
                    print(inst)
                    raise

    return instance


# =============================================================================
# For testing purposes, define a function which can interact with a running
# paraview or vtk web application service.
# =============================================================================
def launch_web_test(*args, **kwargs):
    """
    This function loads a python file as a module (with no package), and then
    instantiates the class it must contain, and finally executes the run_test()
    method of the class (which the class may override, but which is defined in
    both of the testing base classes, WebTest and ImageComparatorBaseClass).
    After the run_test() method finishes, this function will stop the web
    server if required.  This function expects some keyword arguments will be
    present in order for it to complete it's task:

        kwargs['serverOpts']: An object containing all the parameters used
        to start the web service.  Some of them will be used in the test script
        in order perform the test.  For example, the port on which the server
        was started will be required in order to connect to the server.

        kwargs['testScript']: The full path to the python file containing the
        testing subclass.
    """

    serverOpts = None
    testScriptFile = None

    # This is really the thing all test scripts will need: access to all
    # the options used to start the server process.
    if "serverOpts" in kwargs:
        serverOpts = kwargs["serverOpts"]
        # print 'These are the serverOpts we got: '
        # print serverOpts

    # Get the full path to the test script
    if "testScript" in kwargs:
        testScriptFile = kwargs["testScript"]

    testName = "unknown"

    # Check for a test file (python file)
    if testScriptFile is None:
        print("No test script file found, no test script will be run.")
        test_fail(testName)

    # The test name will be generated from the python script name, so
    # match and capture a bunch of contiguous characters which are
    # not '.', '\', or '/', followed immediately by the string '.py'.
    fnamePattern = re.compile(r"([^\.\/\\\]+)\.py")
    fmatch = re.search(fnamePattern, testScriptFile)
    if fmatch:
        testName = fmatch.group(1)
    else:
        print(
            "Unable to parse testScriptFile ("
            + str(testScriptfile)
            + "), no test will be run"
        )
        test_fail(testName)

    # If we successfully got a test name, we are ready to try and run the test
    if testName != "unknown":

        # Output file and baseline file names are generated from the test name
        imgFileName = testName + ".png"
        knownGoodFileName = concat_paths(serverOpts.baselineImgDir, imgFileName)
        tempDir = serverOpts.tmpDirectory
        testImgFileName = serverOpts.testImgFile

        testBrowser = test_module_browsers.index(serverOpts.useBrowser)

        # Now try to instantiate and run the test
        try:
            testInstance = instantiate_test_subclass(
                testScriptFile,
                testname=testName,
                host=serverOpts.host,
                port=serverOpts.port,
                browser=testBrowser,
                filename=testImgFileName,
                baseline=knownGoodFileName,
                temporaryDir=tempDir,
            )

            # If we were able to instantiate the test, run it, otherwise we
            # consider it a failure.
            if testInstance is not None:
                try:
                    testInstance.run_test()
                except DependencyError as derr:
                    # TODO: trigger return SKIP_RETURN_CODE when CMake 3 is required
                    print(
                        "Some dependency of this test was not met, allowing it to pass"
                    )
                    test_pass(testName)
            else:
                print("Unable to instantiate test instance, failing test")
                test_fail(testName)
                return

        except Exception as inst:
            import sys, traceback

            tb = sys.exc_info()[2]
            print("Caught an exception while running test script:")
            print("  " + str(type(inst)))
            print("  " + str(inst))
            print("  " + "".join(traceback.format_tb(tb)))
            test_fail(testName)

    # If we were passed a cleanup method to run after testing, invoke it now
    if "cleanupMethod" in serverOpts:
        serverOpts["cleanupMethod"]()


# =============================================================================
# To keep the service module clean, we'll process the test results here, given
# the test result object we generated in "launch_web_test".  It is
# passed back to this function after the service has completed.  Failure of
# of the test is indicated by raising an exception in here.
# =============================================================================
def finalize():
    """
    This function checks the module's global test_module_comm_queue variable for a
    test result.  If one is found and the result is 'fail', then this function
    raises an exception to communicate the failure to the CTest framework.

    In order for a test result to be found in the test_module_comm_queue variable,
    the test script must have called either the testPass or testFail functions
    provided by this test module before returning.
    """

    global test_module_comm_queue

    if test_module_comm_queue is not None:
        resultObject = test_module_comm_queue.get()

        failedATest = False

        for testName in resultObject:
            testResult = resultObject[testName]
            if testResult == "fail":
                print("  Test -> " + testName + ": " + testResult)
                failedATest = True

        if failedATest is True:
            raise Exception(
                "At least one of the requested tests failed.  "
                + "See detailed output, above, for more information"
            )