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 info(self, message):
43        self.__logger.info(message)
44
45    def warning(self, message):
46        self.__logger.warning(message)
47
48class FixedPointTester():
49    """ Made for testing a particular Qn.m number
50
51    As a convention, we use:
52        * n is the fractional part
53        * m is the integral part
54
55    This class computes several specific numbers for a given Qn.m number.
56
57    For each of those numbers, we run 4 checks:
58        * Bound check
59        * Sanity check
60        * Consistency check
61        * Bijectivity check
62    Which are documented below.
63    """
64    def __init__(self, pfwClient, size, integral, fractional):
65        self._pfwClient = pfwClient
66        self._paramPath = '/Test/test/%d/q%d.%d' % (size, integral, fractional)
67
68        # quantum is the step we have between two numbers
69        # encoded in Qn.m format
70        self._quantum = 2 ** -fractional
71
72        # The maximum value we can encode for a given Qn.m.
73        # Since we also need to encode the 0, we have one quantum missing on
74        # the positive maximum
75        self._upperAllowedBound = (2 ** integral) - self._quantum
76
77        # The minimum value that we can encode for a given Qn.m.
78        # This one does not need a quantum substraction since we already did
79        # that on the maximum
80        self._lowerAllowedBound = -(2 ** integral)
81
82        self._shouldWork = [
83                Decimal(0),
84                Decimal(self._lowerAllowedBound),
85                Decimal(self._upperAllowedBound)
86                ]
87
88        # bigValue is to be sure a value far out of range is refused
89        bigValue = (2 * self._quantum)
90        # little is to be sure a value just out of range is refused
91        littleValue = 10 ** -(int(fractional * log10(2)))
92        self._shouldBreak = [
93                Decimal(self._lowerAllowedBound) - Decimal(bigValue),
94                Decimal(self._upperAllowedBound) + Decimal(bigValue),
95                Decimal(self._lowerAllowedBound) - Decimal(littleValue),
96                Decimal(self._upperAllowedBound) + Decimal(littleValue)
97                ]
98
99        self._chainingTests = [
100                ('Bound', self.checkBounds),
101                ('Sanity', self.checkSanity),
102                ('Consistency', self.checkConsistency),
103                ('Bijectivity', self.checkBijectivity)]
104
105
106    def run(self):
107        """ Runs the test suite for a given Qn.m number
108        """
109
110        runSuccess = True
111
112        for value in self._shouldWork:
113            value = value.normalize()
114            print('Testing %s for %s' % (value, self._paramPath))
115
116            for testName, testFunc in self._chainingTests:
117                value, success = testFunc(value)
118                if not success:
119                    runSuccess = False
120                    print("%s ERROR for %s" % (testName, self._paramPath))
121                    break
122
123        for value in self._shouldBreak:
124            value = value.normalize()
125            print('Testing invalid value %s for %s' % (value, self._paramPath))
126            value, success = self.checkBounds(value)
127            if success:
128                runSuccess = False
129                print("ERROR: This test should have failed but it has not")
130
131        return runSuccess
132
133    def checkBounds(self, valueToSet):
134        """ Checks if we are able to set valueToSet via the parameter-framework
135
136        valueToSet -- the value we are trying to set
137
138        returns: the value we are trying to set
139        returns: True if we are able to set, False otherwise
140        """
141        (success, errorMsg) = self._pfwClient.set(self._paramPath, str(valueToSet))
142
143        return valueToSet, success
144
145
146    def checkSanity(self, valuePreviouslySet):
147        """ Checks if the value we get is still approximately the same
148        as we attempted to set. The value can have a slight round error which
149        is tolerated.
150
151        valuePreviouslySet -- the value we had previously set
152
153        returns: the value the parameter-framework returns us after the get
154        returns: True if we are able to set, False otherwise
155        """
156        firstGet = self._pfwClient.get(self._paramPath)
157
158        try:
159            returnValue = Decimal(firstGet)
160        except ValueError:
161            print("ERROR: Can't convert %s to a decimal" % firstGet)
162            return firstGet, False
163
164        upperAllowedValue = Decimal(valuePreviouslySet) + (Decimal(self._quantum) / Decimal(2))
165        lowerAllowedValue = Decimal(valuePreviouslySet) - (Decimal(self._quantum) / Decimal(2))
166
167        if not (lowerAllowedValue <= returnValue <= upperAllowedValue):
168            print('%s <= %s <= %s is not true' %
169                    (lowerAllowedValue, returnValue, upperAllowedValue))
170            return firstGet, False
171
172        return firstGet, True
173
174    def checkConsistency(self, valuePreviouslyGotten):
175        """ Checks if we are able to set the value that the parameter framework
176        just returned to us.
177
178        valuePreviouslyGotten -- the value we are trying to set
179
180        valueToSet -- the value we are trying to set
181        returns: True if we are able to set, False otherwise
182        """
183        (success, errorMsg) = pfw.set(self._paramPath, valuePreviouslyGotten)
184
185        return valuePreviouslyGotten, success
186
187    def checkBijectivity(self, valuePreviouslySet):
188        """ Checks that the second get value is strictly equivalent to the
189        consistency set. This ensures that the parameter-framework behaves as
190        expected.
191
192        valuePreviouslySet -- the value we had previously set
193
194        returns: value the parameter-framework returns us after the second get
195        returns: True if we are able to set, False otherwise
196        """
197        secondGet = pfw.get(self._paramPath)
198
199        if secondGet != valuePreviouslySet:
200            return secondGet, False
201
202        return secondGet, True
203
204class PfwClient():
205
206    def __init__(self, configPath):
207        self._instance = PyPfw.ParameterFramework(configPath)
208
209        self._logger = PfwLogger()
210        self._instance.setLogger(self._logger)
211        # Disable the remote interface because we don't need it and it might
212        # get in the way (e.g. the port is already in use)
213        self._instance.setForceNoRemoteInterface(True)
214
215        self._instance.start()
216        self._instance.setTuningMode(True)
217
218    def set(self, parameter, value):
219        print('set %s <--- %s' % (parameter, value))
220        (success, _, errorMsg) = self._instance.accessParameterValue(parameter, str(value), True)
221        return success, errorMsg
222
223    def get(self, parameter):
224        (success, value, errorMsg) = self._instance.accessParameterValue(parameter, "", False)
225        if not success:
226            raise Exception("A getParameter failed, which is unexpected. The"
227                            "parameter-framework answered:\n%s" % errorMsg)
228
229        print('get %s ---> %s' % (parameter, value))
230        return value
231
232if __name__ == '__main__':
233    # It is necessary to add a ./ in front of the path, otherwise the parameter-framework
234    # does not recognize the string as a path.
235    pfw = PfwClient('./ParameterFrameworkConfiguration.xml')
236
237    success = True
238
239    for size in [8, 16, 32]:
240        for integral in range(0,  size):
241            for fractional in range (0,  size - integral):
242                tester = FixedPointTester(pfw, size, integral, fractional)
243                success = tester.run() and success
244
245    exit(0 if success else 1)
246