Passed
Push — master ( 694e2f...150a8e )
by Dean
09:10 queued 06:18
created

TestBase.on_exception()   A

Complexity

Conditions 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2.864
Metric Value
cc 2
dl 0
loc 8
ccs 2
cts 5
cp 0.4
crap 2.864
rs 9.4285
1 1
from plugin.core.helpers.variable import merge
2
3 1
from subprocess import Popen
4 1
import json
5 1
import logging
6 1
import os
7 1
import subprocess
8 1
import sys
9
10 1
CURRENT_PATH = os.path.abspath(__file__)
11 1
HOST_PATH = os.path.join(os.path.dirname(CURRENT_PATH), 'host.py')
12
13 1
log = logging.getLogger(__name__)
14
15
16 1
class BaseTest(object):
17 1
    name = None
18 1
    optional = False
19
20 1
    @classmethod
21
    def run(cls, search_paths):
22 1
        metadata = {}
23
24 1
        message = None
25 1
        success = None
26
27
        # Retrieve names of test functions
28 1
        names = [
29
            name for name in dir(cls)
30
            if name.startswith('test_')
31
        ]
32
33 1
        if not names:
34
            return cls.build_failure('No tests defined')
35
36
        # Run tests
37 1
        for name in names:
38
            # Ensure function exists
39 1
            if not hasattr(cls, name):
40
                return cls.build_failure('Unable to find function: %r' % name)
41
42
            # Run test
43 1
            try:
44 1
                result = cls.spawn(name, search_paths)
45
46
                # Merge test result into `metadata`
47 1
                merge(metadata, result, recursive=True)
48
49
                # Test successful
50 1
                message = None
51 1
                success = True
52
            except Exception, ex:
53
                if success:
54
                    continue
55
56
                message = ex.message
57
                success = False
58
59 1
        if not success:
60
            # Trigger event
61
            cls.on_failure(message)
62
63
            # Build result
64
            return cls.build_failure(message)
65
66
        # Trigger event
67 1
        cls.on_success(metadata)
68
69
        # Build result
70 1
        return cls.build_success(metadata)
71
72 1
    @classmethod
73
    def spawn(cls, name, search_paths):
74
        # Find path to python executable
75 1
        python_exe = cls.find_python_executable()
76
77 1
        if not python_exe:
78
            raise Exception('Unable to find python executable')
79
80
        # Ensure test host exists
81 1
        if not os.path.exists(HOST_PATH):
82
            raise Exception('Unable to find "host.py" script')
83
84
        # Build test process arguments
85 1
        args = [
86
            python_exe, HOST_PATH,
87
            '--module', cls.__module__,
88
            '--name', name,
89
90
            '--search-paths="%s"' % (
91
                ';'.join(search_paths)
92
            ),
93
        ]
94
95
        # Spawn test (in sub-process)
96 1
        log.debug('Starting test: %s:%s', cls.__module__, name)
97
98 1
        process = Popen(
99
            args,
100
            stdout=subprocess.PIPE,
101
            stderr=subprocess.PIPE
102
        )
103
104
        # Wait for test to complete
105 1
        stdout, stderr = process.communicate()
106
107 1
        if stderr:
108
            log.debug('Test returned messages:\n%s', stderr.replace("\r\n", "\n"))
109
110
        # Parse output
111 1
        result = None
112
113 1
        if stdout:
114 1
            try:
115 1
                result = json.loads(stdout)
116
            except Exception, ex:
117
                log.warn('Invalid output returned %r - %s', stdout, ex, exc_info=True)
118
119
        # Build result
120 1
        if process.returncode != 0:
121
            # Test failed
122
            if result and 'message' in result:
123
                raise Exception(result['message'])
124
125
            raise Exception('Unknown error (code: %s)' % process.returncode)
126
127
        # Test successful
128 1
        return result
129
130 1
    @classmethod
131
    def find_python_executable(cls):
132 1
        candidates = [sys.executable]
133
134
        # Add candidates relative to the PMS home directory
135 1
        pms_home = os.environ.get('PLEX_MEDIA_SERVER_HOME')
136
137 1
        if pms_home and os.path.exists(pms_home):
138
            candidates.append(os.path.join(pms_home, 'Resources', 'Plex Script Host'))
139
            candidates.append(os.path.join(pms_home, 'Resources', 'Python', 'bin', 'python'))
140
141
        # Use first candidate that exists
142 1
        for path in candidates:
143 1
            if os.path.exists(path):
144 1
                return path
145
146
        return None
147
148
    #
149
    # Events
150
    #
151
152 1
    @classmethod
153
    def on_failure(cls, message):
154
        pass
155
156 1
    @classmethod
157
    def on_success(cls, metadata):
158 1
        pass
159
160
    #
161
    # Helpers
162
    #
163
164 1
    @classmethod
165 1
    def build_exception(cls, message, exc_info=None):
166
        if exc_info is None:
167
            exc_info = sys.exc_info()
168
169
        return cls.build_failure(
170
            message,
171
            exc_info=exc_info
172
        )
173
174 1
    @classmethod
175
    def build_failure(cls, message, **kwargs):
176
        result = {
177
            'success': False,
178
            'message': message
179
        }
180
181
        # Merge extra attributes
182
        merge(result, kwargs)
183
184
        return result
185
186 1
    @staticmethod
187
    def build_success(metadata):
188 1
        return {
189
            'success': True,
190
            'metadata': metadata
191
        }
192