1#!/usr/bin/env python 2# 3# Copyright (C) 2020 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 parse 19import sys 20 21from abc import ABCMeta 22from abc import abstractmethod 23from ply import lex 24from ply import yacc 25import target_file_utils 26 27 28def repeat_rule(to_repeat, zero_ok=False): 29 ''' 30 From a given rule, generates a rule that allows consecutive items 31 of that rule. Instances are collected in a list. 32 ''' 33 34 def p_multiple(self, p): 35 if len(p) == 2 and zero_ok: 36 p[0] = [] 37 elif len(p) == 2: 38 p[0] = [p[1]] 39 else: 40 p[0] = p[1] + [p[2]] 41 42 func = p_multiple 43 format_tuple = (to_repeat, to_repeat, to_repeat, 'empty' 44 if zero_ok else to_repeat) 45 func.__doc__ = '%ss : %ss %s \n| %s' % format_tuple 46 return func 47 48 49def literal_token(tok): 50 ''' 51 A compact function to specify literal string tokens when. 52 they need to take precedence over a generic string, 53 Among these tokens precedence is decided in alphabetic order. 54 ''' 55 56 def t_token(self, t): 57 return t 58 59 func = t_token 60 func.__doc__ = tok 61 return func 62 63 64class KernelProcFileTestBase(object): 65 """ 66 An abstract test for the formatting of a procfs file. Individual 67 files can inherit from this class. 68 69 New parsing rules can be defined in the form of p_RULENAME, and 70 similarly new tokens can be defined as t_TOKENNAME. 71 72 Child class should also specify a `start` variable to give the starting rule. 73 """ 74 75 __metaclass__ = ABCMeta 76 77 def t_HEX_LITERAL(self, t): 78 r'0x[a-f0-9]+' 79 t.value = int(t.value, 0) 80 return t 81 82 def t_FLOAT(self, t): 83 r'([0-9]+[.][0-9]*|[0-9]*[.][0-9]+)' 84 t.value = float(t.value) 85 return t 86 87 def t_NUMBER(self, t): 88 r'\d+' 89 t.value = int(t.value) 90 return t 91 92 t_PATH = r'/[^\0]+' 93 t_COLON = r':' 94 t_EQUALS = r'=' 95 t_COMMA = r',' 96 t_PERIOD = 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 set_api_level(self, dut): 140 self.api_level = dut.getLaunchApiLevel(strict=False) 141 142 def parse_line(self, rule, line, custom={}): 143 """Parse a line of text with the parse library. 144 145 Args: 146 line: string, a line of text 147 rule: string, a format rule. See parse documentation 148 custom: dict, maps to custom type conversion functions 149 150 Returns: 151 list, information parsed from the line 152 153 Raises: 154 SyntaxError: if the line could not be parsed. 155 """ 156 parsed = parse.parse(rule, line, custom) 157 if parsed is None: 158 raise SyntaxError("Failed to parse line %s according to rule %s" % 159 (line, rule)) 160 return list(parsed) 161 162 def parse_contents(self, file_contents): 163 """Using the internal parser, parse the contents. 164 165 Args: 166 file_contents: string, entire contents of a file 167 168 Returns: 169 list, a parsed representation of the file 170 171 Raises: 172 SyntaxError: if the file could not be parsed 173 """ 174 return self.parser.parse(file_contents, lexer=self.lexer) 175 176 @abstractmethod 177 def get_path(self): 178 """Returns the full path of this proc file (string).""" 179 pass 180 181 def prepare_test(self, dut): 182 """Performs any actions necessary before testing the proc file. 183 184 Args: 185 shell: shell object, for preparation that requires device access 186 187 Returns: 188 boolean, True if successful. 189 """ 190 return True 191 192 def file_optional(self, shell=None, dut=None): 193 """Performs any actions necessary to return if file is allowed to be absent 194 195 Args: 196 shell: shell object, to run commands on the device side 197 dut: AndroidDevice object to access functions and properties of that object 198 199 Returns: 200 boolean, True if file is allowed to be absent. 201 """ 202 return False 203 204 def result_correct(self, parse_result): 205 """Returns: True if the parsed result meets the requirements (boolean).""" 206 return True 207 208 def test_format(self): 209 """Returns: 210 boolean, True if the file should be read and its format tested. 211 False if only the existence and permission should be tested. 212 """ 213 return True 214 215 def get_permission_checker(self): 216 """Gets the function handle to use for validating file permissions. 217 218 Return the function that will check if the permissions are correct. 219 By default, return the IsReadOnly function from target_file_utils. 220 221 Returns: 222 function which takes one argument (the unix file permission bits 223 in octal format) and returns True if the permissions are correct, 224 False otherwise. 225 """ 226 return target_file_utils.IsReadOnly 227