1 #!/usr/bin/env python 2 # 3 # Copyright (C) 2017 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 18 import os 19 import parse 20 import sys 21 22 from abc import ABCMeta 23 from abc import abstractmethod 24 from ply import lex 25 from ply import yacc 26 from vts.utils.python.file import target_file_utils 27 28 29 def repeat_rule(to_repeat, zero_ok=False): 30 ''' 31 From a given rule, generates a rule that allows consecutive items 32 of that rule. Instances are collected in a list. 33 ''' 34 35 def p_multiple(self, p): 36 if len(p) == 2 and zero_ok: 37 p[0] = [] 38 elif len(p) == 2: 39 p[0] = [p[1]] 40 else: 41 p[0] = p[1] + [p[2]] 42 43 func = p_multiple 44 format_tuple = (to_repeat, to_repeat, to_repeat, 'empty' 45 if zero_ok else to_repeat) 46 func.__doc__ = '%ss : %ss %s \n| %s' % format_tuple 47 return func 48 49 50 def literal_token(tok): 51 ''' 52 A compact function to specify literal string tokens when. 53 they need to take precedence over a generic string, 54 Among these tokens precedence is decided in alphabetic order. 55 ''' 56 57 def t_token(self, t): 58 return t 59 60 func = t_token 61 func.__doc__ = tok 62 return func 63 64 65 class KernelProcFileTestBase(object): 66 """ 67 An abstract test for the formatting of a procfs file. Individual 68 files can inherit from this class. 69 70 New parsing rules can be defined in the form of p_RULENAME, and 71 similarly new tokens can be defined as t_TOKENNAME. 72 73 Child class should also specify a `start` variable to give the starting rule. 74 """ 75 76 __metaclass__ = ABCMeta 77 78 def t_HEX_LITERAL(self, t): 79 r'0x[a-f0-9]+' 80 t.value = int(t.value, 0) 81 return t 82 83 def t_FLOAT(self, t): 84 r'([0-9]+[.][0-9]*|[0-9]*[.][0-9]+)' 85 t.value = float(t.value) 86 return t 87 88 def t_NUMBER(self, t): 89 r'\d+' 90 t.value = int(t.value) 91 return t 92 93 t_PATH = r'/[^\0]+' 94 t_COLON = r':' 95 t_EQUALS = r'=' 96 t_COMMA = r',' 97 t_STRING = r'[a-zA-Z\(\)_0-9\-@]+' 98 99 t_TAB = r'\t' 100 t_SPACE = r'[ ]' 101 102 def t_DASH(self, t): 103 r'\-' 104 return t 105 106 def t_NEWLINE(self, t): 107 r'\n' 108 t.lexer.lineno += len(t.value) 109 return t 110 111 t_ignore = '' 112 113 def t_error(self, t): 114 raise SyntaxError("Illegal character '%s' in line %d '%s'" % \ 115 (t.value[0], t.lexer.lineno, t.value.split()[0])) 116 117 p_SPACEs = repeat_rule('SPACE', zero_ok=True) 118 119 def p_error(self, p): 120 raise SyntaxError("Parsing error at token %s in line %d" % 121 (p, p.lexer.lineno)) 122 123 def p_empty(self, p): 124 'empty :' 125 pass 126 127 def __init__(self): 128 self.tokens = [ 129 t_name[2:] for t_name in dir(self) 130 if len(t_name) > 2 and t_name[:2] == 't_' 131 ] 132 self.tokens.remove('error') 133 self.tokens.remove('ignore') 134 self.lexer = lex.lex(module=self) 135 # (Change logger output stream if debugging) 136 self.parser = yacc.yacc(module=self, write_tables=False, \ 137 errorlog=yacc.PlyLogger(sys.stderr)) #open(os.devnull, 'w'))) 138 139 def parse_line(self, rule, line, custom={}): 140 """Parse a line of text with the parse library. 141 142 Args: 143 line: string, a line of text 144 rule: string, a format rule. See parse documentation 145 custom: dict, maps to custom type conversion functions 146 147 Returns: 148 list, information parsed from the line 149 150 Raises: 151 SyntaxError: if the line could not be parsed. 152 """ 153 parsed = parse.parse(rule, line, custom) 154 if parsed is None: 155 raise SyntaxError("Failed to parse line %s according to rule %s" % 156 (line, rule)) 157 return list(parsed) 158 159 def parse_contents(self, file_contents): 160 """Using the internal parser, parse the contents. 161 162 Args: 163 file_contents: string, entire contents of a file 164 165 Returns: 166 list, a parsed representation of the file 167 168 Raises: 169 SyntaxError: if the file could not be parsed 170 """ 171 return self.parser.parse(file_contents, lexer=self.lexer) 172 173 @abstractmethod 174 def get_path(self): 175 """Returns the full path of this proc file (string).""" 176 pass 177 178 def prepare_test(self, shell): 179 """Performs any actions necessary before testing the proc file. 180 181 Args: 182 shell: shell object, for preparation that requires device access 183 184 Returns: 185 boolean, True if successful. 186 """ 187 return True 188 189 def result_correct(self, parse_result): 190 """Returns: True if the parsed result meets the requirements (boolean).""" 191 return True 192 193 def test_format(self): 194 """Returns: 195 boolean, True if the file should be read and its format tested. 196 False if only the existence and permission should be tested. 197 """ 198 return True 199 200 def get_permission_checker(self): 201 """Gets the function handle to use for validating file permissions. 202 203 Return the function that will check if the permissions are correct. 204 By default, return the IsReadOnly function from target_file_utils. 205 206 Returns: 207 function which takes one argument (the unix file permission bits 208 in octal format) and returns True if the permissions are correct, 209 False otherwise. 210 """ 211 return target_file_utils.IsReadOnly 212