1#!/usr/bin/python2.7
2#
3# Copyright (c) 2014-2015, Intel Corporation
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without modification,
7# are permitted provided that the following conditions are met:
8#
9# 1. Redistributions of source code must retain the above copyright notice, this
10# list of conditions and the following disclaimer.
11#
12# 2. Redistributions in binary form must reproduce the above copyright notice,
13# this list of conditions and the following disclaimer in the documentation and/or
14# other materials provided with the distribution.
15#
16# 3. Neither the name of the copyright holder nor the names of its contributors
17# may be used to endorse or promote products derived from this software without
18# specific prior written permission.
19#
20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
24# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
27# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31import PyPfw
32
33import logging
34from decimal import Decimal
35from math import log10
36
37class PfwLogger(PyPfw.ILogger):
38    def __init__(self):
39        super(PfwLogger, self).__init__()
40        self.__logger = logging.root.getChild("parameter-framework")
41
42    def log(self, is_warning, message):
43        log_func = self.__logger.warning if is_warning else self.__logger.info
44        log_func(message)
45
46class FixedPointTester():
47    """ Made for testing a particular Qn.m number
48
49    As a convention, we use:
50        * n is the fractional part
51        * m is the integral part
52
53    This class computes several specific numbers for a given Qn.m number.
54
55    For each of those numbers, we run 4 checks:
56        * Bound check
57        * Sanity check
58        * Consistency check
59        * Bijectivity check
60    Which are documented below.
61    """
62    def __init__(self, pfwClient, size, integral, fractional):
63        self._pfwClient = pfwClient
64        self._paramPath = '/Test/test/%d/q%d.%d' % (size, integral, fractional)
65
66        # quantum is the step we have between two numbers
67        # encoded in Qn.m format
68        self._quantum = 2 ** -fractional
69
70        # The maximum value we can encode for a given Qn.m.
71        # Since we also need to encode the 0, we have one quantum missing on
72        # the positive maximum
73        self._upperAllowedBound = (2 ** integral) - self._quantum
74
75        # The minimum value that we can encode for a given Qn.m.
76        # This one does not need a quantum substraction since we already did
77        # that on the maximum
78        self._lowerAllowedBound = -(2 ** integral)
79
80        self._shouldWork = [
81                Decimal(0),
82                Decimal(self._lowerAllowedBound),
83                Decimal(self._upperAllowedBound)
84                ]
85
86        # bigValue is to be sure a value far out of range is refused
87        bigValue = (2 * self._quantum)
88        # little is to be sure a value just out of range is refused
89        littleValue = 10 ** -(int(fractional * log10(2)))
90        self._shouldBreak = [
91                Decimal(self._lowerAllowedBound) - Decimal(bigValue),
92                Decimal(self._upperAllowedBound) + Decimal(bigValue),
93                Decimal(self._lowerAllowedBound) - Decimal(littleValue),
94                Decimal(self._upperAllowedBound) + Decimal(littleValue)
95                ]
96
97        self._chainingTests = [
98                ('Bound', self.checkBounds),
99                ('Sanity', self.checkSanity),
100                ('Consistency', self.checkConsistency),
101                ('Bijectivity', self.checkBijectivity)]
102
103
104    def run(self):
105        """ Runs the test suite for a given Qn.m number
106        """
107
108        runSuccess = True
109
110        for value in self._shouldWork:
111            value = value.normalize()
112            print('Testing %s for %s' % (value, self._paramPath))
113
114            for testName, testFunc in self._chainingTests:
115                value, success = testFunc(value)
116                if not success:
117                    runSuccess = False
118                    print("%s ERROR for %s" % (testName, self._paramPath))
119                    break
120
121        for value in self._shouldBreak:
122            value = value.normalize()
123            print('Testing invalid value %s for %s' % (value, self._paramPath))
124            value, success = self.checkBounds(value)
125            if success:
126                runSuccess = False
127                print("ERROR: This test should have failed but it has not")
128
129        return runSuccess
130
131    def checkBounds(self, valueToSet):
132        """ Checks if we are able to set valueToSet via the parameter-framework
133
134        valueToSet -- the value we are trying to set
135
136        returns: the value we are trying to set
137        returns: True if we are able to set, False otherwise
138        """
139        (success, errorMsg) = self._pfwClient.set(self._paramPath, str(valueToSet))
140
141        return valueToSet, success
142
143
144    def checkSanity(self, valuePreviouslySet):
145        """ Checks if the value we get is still approximately the same
146        as we attempted to set. The value can have a slight round error which
147        is tolerated.
148
149        valuePreviouslySet -- the value we had previously set
150
151        returns: the value the parameter-framework returns us after the get
152        returns: True if we are able to set, False otherwise
153        """
154        firstGet = self._pfwClient.get(self._paramPath)
155
156        try:
157            returnValue = Decimal(firstGet)
158        except ValueError:
159            print("ERROR: Can't convert %s to a decimal" % firstGet)
160            return firstGet, False
161
162        upperAllowedValue = Decimal(valuePreviouslySet) + (Decimal(self._quantum) / Decimal(2))
163        lowerAllowedValue = Decimal(valuePreviouslySet) - (Decimal(self._quantum) / Decimal(2))
164
165        if not (lowerAllowedValue <= returnValue <= upperAllowedValue):
166            print('%s <= %s <= %s is not true' %
167                    (lowerAllowedValue, returnValue, upperAllowedValue))
168            return firstGet, False
169
170        return firstGet, True
171
172    def checkConsistency(self, valuePreviouslyGotten):
173        """ Checks if we are able to set the value that the parameter framework
174        just returned to us.
175
176        valuePreviouslyGotten -- the value we are trying to set
177
178        valueToSet -- the value we are trying to set
179        returns: True if we are able to set, False otherwise
180        """
181        (success, errorMsg) = pfw.set(self._paramPath, valuePreviouslyGotten)
182
183        return valuePreviouslyGotten, success
184
185    def checkBijectivity(self, valuePreviouslySet):
186        """ Checks that the second get value is strictly equivalent to the
187        consistency set. This ensures that the parameter-framework behaves as
188        expected.
189
190        valuePreviouslySet -- the value we had previously set
191
192        returns: value the parameter-framework returns us after the second get
193        returns: True if we are able to set, False otherwise
194        """
195        secondGet = pfw.get(self._paramPath)
196
197        if secondGet != valuePreviouslySet:
198            return secondGet, False
199
200        return secondGet, True
201
202class PfwClient():
203
204    def __init__(self, configPath):
205        self._instance = PyPfw.ParameterFramework(configPath)
206
207        self._logger = PfwLogger()
208        self._instance.setLogger(self._logger)
209        # Disable the remote interface because we don't need it and it might
210        # get in the way (e.g. the port is already in use)
211        self._instance.setForceNoRemoteInterface(True)
212
213        self._instance.start()
214        self._instance.setTuningMode(True)
215
216    def set(self, parameter, value):
217        print('set %s <--- %s' % (parameter, value))
218        (success, _, errorMsg) = self._instance.accessParameterValue(parameter, str(value), True)
219        return success, errorMsg
220
221    def get(self, parameter):
222        (success, value, errorMsg) = self._instance.accessParameterValue(parameter, "", False)
223        if not success:
224            raise Exception("A getParameter failed, which is unexpected. The"
225                            "parameter-framework answered:\n%s" % errorMsg)
226
227        print('get %s ---> %s' % (parameter, value))
228        return value
229
230if __name__ == '__main__':
231    # It is necessary to add a ./ in front of the path, otherwise the parameter-framework
232    # does not recognize the string as a path.
233    pfw = PfwClient('./ParameterFrameworkConfiguration.xml')
234
235    success = True
236
237    for size in [8, 16, 32]:
238        for integral in range(0,  size):
239            for fractional in range (0,  size - integral):
240                tester = FixedPointTester(pfw, size, integral, fractional)
241                success = tester.run() and success
242
243    exit(0 if success else 1)
244