1#!/usr/bin/env python
2#
3# Copyright (C) 2016 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import math
19import os
20import struct
21import unittest
22
23from vts.utils.python.coverage import parser
24
25MAGIC = 0x67636e6f
26
27
28class MockStream(object):
29    """MockStream object allows for mocking file reading behavior.
30
31    Allows for adding integers and strings to the file stream in a
32    specified byte format and then reads them as if from a file.
33
34    Attributes:
35        content: the byte list representing a file stream
36        cursor: the index into the content such that everything before it
37                has been read already.
38    """
39    BYTES_PER_WORD = 4
40
41    def __init__(self, magic=MAGIC, format='<'):
42        self.format = format
43        self.magic = magic
44        version = struct.unpack(format + 'I', '*802')[0]
45        self.content = struct.pack(format + 'III', magic, version, 0)
46        self.cursor = 0
47
48    @classmethod
49    def concat_int(cls, stream, integer):
50        """Returns the stream with a binary formatted integer concatenated.
51
52        Args:
53            stream: the stream to which the integer will be concatenated.
54            integer: the integer to be concatenated to the content stream.
55            format: the string format decorator to apply to the integer.
56
57        Returns:
58            The content with the binary-formatted integer concatenated.
59        """
60        new_content = stream.content + struct.pack(stream.format + 'I',
61                                                   integer)
62        s = MockStream(stream.magic, stream.format)
63        s.content = new_content
64        s.cursor = stream.cursor
65        return s
66
67    @classmethod
68    def concat_int64(cls, stream, integer):
69        """Returns the stream with a binary formatted int64 concatenated.
70
71        Args:
72            stream: the stream to which the integer will be concatenated.
73            integer: the 8-byte int to be concatenated to the content stream.
74            format: the string format decorator to apply to the long.
75
76        Returns:
77            The content with the binary-formatted int64 concatenated.
78        """
79        lo = ((1 << 32) - 1) & integer
80        hi = (integer - lo) >> 32
81        new_content = stream.content + struct.pack(stream.format + 'II', lo,
82                                                   hi)
83        s = MockStream(stream.magic, stream.format)
84        s.content = new_content
85        s.cursor = stream.cursor
86        return s
87
88    @classmethod
89    def concat_string(cls, stream, string):
90        """Returns the stream with a binary formatted string concatenated.
91
92        Preceeds the string with an integer representing the number of
93        words in the string. Pads the string so that it is word-aligned.
94
95        Args:
96            stream: the stream to which the string will be concatenated.
97            string: the string to be concatenated to the content stream.
98            format: the string format decorator to apply to the integer.
99
100        Returns:
101            The content with the formatted binary string concatenated.
102        """
103        byte_count = len(string)
104        word_count = int(
105            math.ceil(byte_count * 1.0 / MockStream.BYTES_PER_WORD))
106        padding = '\x00' * (
107            MockStream.BYTES_PER_WORD * word_count - byte_count)
108        new_content = stream.content + struct.pack(
109            stream.format + 'I', word_count) + bytes(string + padding)
110        s = MockStream(stream.magic, stream.format)
111        s.content = new_content
112        s.cursor = stream.cursor
113        return s
114
115    def read(self, n_bytes):
116        """Reads the specified number of bytes from the content stream.
117
118        Args:
119            n_bytes: integer number of bytes to read.
120
121        Returns:
122            The string of length n_bytes beginning at the cursor location
123            in the content stream.
124        """
125        content = self.content[self.cursor:self.cursor + n_bytes]
126        self.cursor += n_bytes
127        return content
128
129
130class ParserTest(unittest.TestCase):
131    """Tests for stream parser of vts.utils.python.coverage.
132
133    Ensures error handling, byte order detection, and correct
134    parsing of integers and strings.
135    """
136
137    def setUp(self):
138        """Creates a stream for each test.
139      """
140        self.stream = MockStream()
141
142    def testLittleEndiannessInitialization(self):
143        """Tests parser init  with little-endian byte order.
144
145        Verifies that the byte-order is correctly detected.
146        """
147        p = parser.GcovStreamParserUtil(self.stream, MAGIC)
148        self.assertEqual(p.format, '<')
149
150    def testBigEndiannessInitialization(self):
151        """Tests parser init with big-endian byte order.
152
153        Verifies that the byte-order is correctly detected.
154        """
155        self.stream = MockStream(format='>')
156        p = parser.GcovStreamParserUtil(self.stream, MAGIC)
157        self.assertEqual(p.format, '>')
158
159    def testReadIntNormal(self):
160        """Asserts that integers are correctly read from the stream.
161
162        Tests the normal case--when the value is actually an integer.
163        """
164        integer = 2016
165        self.stream = MockStream.concat_int(self.stream, integer)
166        p = parser.GcovStreamParserUtil(self.stream, MAGIC)
167        self.assertEqual(p.ReadInt(), integer)
168
169    def testReadIntEof(self):
170        """Asserts that an error is thrown when the EOF is reached.
171        """
172        p = parser.GcovStreamParserUtil(self.stream, MAGIC)
173        self.assertRaises(parser.FileFormatError, p.ReadInt)
174
175    def testReadInt64(self):
176        """Asserts that longs are read correctly.
177        """
178        number = 68719476836
179        self.stream = MockStream.concat_int64(self.stream, number)
180        p = parser.GcovStreamParserUtil(self.stream, MAGIC)
181        self.assertEqual(number, p.ReadInt64())
182
183        self.stream = MockStream(format='>')
184        self.stream = MockStream.concat_int64(self.stream, number)
185        p = parser.GcovStreamParserUtil(self.stream, MAGIC)
186        self.assertEqual(number, p.ReadInt64())
187
188    def testReadStringNormal(self):
189        """Asserts that strings are correctly read from the stream.
190
191        Tests the normal case--when the string is correctly formatted.
192        """
193        test_string = "This is a test."
194        self.stream = MockStream.concat_string(self.stream, test_string)
195        p = parser.GcovStreamParserUtil(self.stream, MAGIC)
196        self.assertEqual(p.ReadString(), test_string)
197
198    def testReadStringError(self):
199        """Asserts that invalid string format raises error.
200
201        Tests when the string length is too short and EOF is reached.
202        """
203        test_string = "This is a test."
204        byte_count = len(test_string)
205        word_count = int(round(byte_count / 4.0))
206        padding = '\x00' * (4 * word_count - byte_count)
207        test_string_padded = test_string + padding
208        content = struct.pack('<I', word_count + 1)  #  will cause EOF error
209        content += bytes(test_string_padded)
210        self.stream.content += content
211        p = parser.GcovStreamParserUtil(self.stream, MAGIC)
212        self.assertRaises(parser.FileFormatError, p.ReadString)
213
214
215if __name__ == "__main__":
216    unittest.main()
217