1# -*- coding: utf-8 -*-
2# ------------------------------------------------------------------------------
3# Name:         test.py
4# Purpose:      Controller for all module tests in music21.
5#
6# Authors:      Christopher Ariza
7#               Michael Scott Cuthbert
8#
9# Copyright:    Copyright © 2009-2012 Michael Scott Cuthbert and the music21 Project
10# License:      BSD, see license.txt
11# ------------------------------------------------------------------------------
12'''
13Controller to run all module tests in the music21 folders.
14
15Runs great, but slowly on multiprocessor systems.
16'''
17
18import doctest
19import sys
20import unittest
21import warnings
22
23from music21 import common
24from music21 import environment
25
26from music21.test import commonTest
27from music21.test import coverageM21
28from music21.test import testRunner
29
30_MOD = 'test.testSingleCoreAll'
31environLocal = environment.Environment(_MOD)
32
33
34# this is designed to be None for all but one system and a Coverage() object
35# for one system.
36cov = coverageM21.getCoverage()
37
38
39def main(testGroup=('test',),
40         restoreEnvironmentDefaults=False,
41         limit=None,
42         verbosity=2,
43         show: bool = True,
44         ):
45    '''
46    Run all tests. Group can be 'test' and/or 'external'.
47    External will not run doctests.
48    '''
49    for group in testGroup:
50        if group not in ('test', 'external'):
51            raise ValueError(f"Valid test groups are 'test' and 'external'; got {group}")
52    commonTest.testImports()
53    s1 = commonTest.defaultDoctestSuite(__name__)
54
55    modGather = commonTest.ModuleGather()
56    modules = modGather.load(restoreEnvironmentDefaults)
57
58    environLocal.printDebug('looking for Test classes...\n')
59    # look over each module and gather doc tests and unittests
60    totalModules = 0
61    sortMods = common.misc.sortModules(modules)
62    # print(dir(sortMods[0]))
63
64    for moduleObject in sortMods:
65        unitTestCases = []
66        if limit is not None:
67            if totalModules > limit:
68                break
69        totalModules += 1
70        # get Test classes in module
71        if not hasattr(moduleObject, 'Test'):
72            environLocal.printDebug(f'{moduleObject} has no Test class')
73        else:
74            if 'test' in testGroup:
75                unitTestCases.append(moduleObject.Test)
76        if not hasattr(moduleObject, 'TestExternal'):
77            pass
78            # environLocal.printDebug(f'{module} has no TestExternal class\n')
79        else:
80            if 'external' in testGroup:
81                if not show:
82                    moduleObject.TestExternal.show = False
83                unitTestCases.append(moduleObject.TestExternal)
84
85        # for each Test class, load this into a suite
86        for testCase in unitTestCases:
87            s2 = unittest.defaultTestLoader.loadTestsFromTestCase(testCase)
88            s1.addTests(s2)
89        if testGroup == ('external',):
90            # don't load doctests for runs consisting solely of TestExternal
91            continue
92        try:
93            s3 = commonTest.defaultDoctestSuite(moduleObject)
94            s1.addTests(s3)
95        except ValueError:
96            environLocal.printDebug(f'{moduleObject} cannot load Doctests')
97            continue
98
99        allLocals = [getattr(moduleObject, x) for x in dir(moduleObject)]
100
101        globs = __import__('music21').__dict__.copy()
102        docTestOptions = (doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE)
103        testRunner.addDocAttrTestsToSuite(s1,
104                                          allLocals,
105                                          outerFilename=moduleObject.__file__,
106                                          globs=globs,
107                                          optionflags=docTestOptions,
108                                          # no checker here
109                                          )
110
111    testRunner.fixDoctests(s1)
112
113    environLocal.printDebug('running Tests...\n')
114
115    with warnings.catch_warnings():
116        warnings.simplefilter('once', RuntimeWarning)  # import modules...
117        warnings.simplefilter('ignore', FutureWarning)  # a lot of these scipy->numpy
118        runner = unittest.TextTestRunner(verbosity=verbosity)
119        finalTestResults = runner.run(s1)
120
121    coverageM21.stopCoverage(cov)
122
123    if (finalTestResults.errors
124            or finalTestResults.failures
125            or finalTestResults.unexpectedSuccesses):
126        returnCode = 1
127    else:
128        returnCode = 0
129
130    return returnCode
131
132
133def ciMain():
134    # the main call for ci tests.
135    # runs Test classes (including doctests)
136    # and TestExternal (without doctests) with show=False
137    # exits with the aggregated returnCode
138    returnCodeTest = main(testGroup=('test',), verbosity=1)
139    # restart coverage if running main() twice
140    coverageM21.startCoverage(cov)
141    returnCodeExternal = main(testGroup=('external',), verbosity=1, show=False)
142    sys.exit(returnCodeTest + returnCodeExternal)
143
144
145# ------------------------------------------------------------------------------
146if __name__ == '__main__':
147    # if optional command line arguments are given, assume they are
148    # test group arguments
149    if len(sys.argv) >= 2:
150        unused_returnCode = main(sys.argv[1:])
151    else:
152        unused_returnCode = main()
153
154