1#!/usr/bin/env ruby 2# Copyright (c) 2021-2022 Huawei Device Co., Ltd. 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# frozen_string_literal: true 15 16# Huawei Technologies Co.,Ltd. 17 18# Class represents full specification with additional information needed for 19# coverage computation 20class Spec 21 attr_reader :data, :orphaned 22 23 def initialize(arr) 24 # Full spec object 25 @data = merge_specs(arr) 26 27 # Array of tests not matching the spec 28 @orphaned = [] 29 30 prepare_spec 31 end 32 33 def load_non_testable(nt_data) 34 nt_data['groups']&.each do |ntg| 35 spec_group = @data['groups'].find { |sg| sg['title'] == ntg['title'] } 36 if spec_group.nil? 37 warn "Non testable group \"#{ntg['title']}\" not found in ISA." 38 else 39 process_non_testable_group(spec_group, ntg) 40 end 41 end 42 end 43 44 def load_tests(testdir, testglob) 45 Dir.glob(testglob, base: testdir) do |f| 46 process_test_file(testdir, f) 47 rescue StandardError => e 48 @orphaned << { 'file' => f, 'error' => e, 'comment' => e.backtrace } 49 end 50 end 51 52 private 53 54 def merge_specs(sources) 55 target = sources[0] 56 sources.drop(1).each { |f| merge_spec(target, f) } 57 target 58 end 59 60 def merge_spec(target, src) 61 %w[prefixes groups properties exceptions verification version min_version chapters].each do |attr| 62 if target[attr] 63 target[attr] += src[attr] if src[attr] 64 else 65 target[attr] = src[attr] 66 end 67 end 68 end 69 70 def prepare_spec 71 @data['groups'].each do |g| 72 prepare_spec_group(g) 73 end 74 end 75 76 def prepare_spec_group(grp) 77 prepare_description(grp) 78 prepare_instructions(grp) 79 prepare_exceptions(grp) 80 prepare_verifications(grp) 81 end 82 83 def prepare_instructions(grp) 84 grp['instructions'].each do |i| 85 i['tests'] = [] 86 i['non_testable'] = false 87 end 88 end 89 90 def prepare_description(grp) 91 grp['description_tests'] = split(grp['description']).map do |da| 92 { 'assertion' => da, 'tests' => [], 'non_testable' => false } 93 end 94 end 95 96 def prepare_exceptions(grp) 97 grp['exceptions_tests'] = grp['exceptions'].map do |e| 98 { 'exception' => e, 'tests' => [], 'non_testable' => false } 99 end 100 end 101 102 def prepare_verifications(grp) 103 grp['verification_tests'] = grp['verification'].map do |v| 104 { 'verification' => v, 'tests' => [], 'non_testable' => false } 105 end 106 end 107 108 def process_non_testable_group(spec_group, ntg) 109 process_non_testable_description(spec_group, ntg) 110 process_non_testable_instructions(spec_group, ntg) 111 process_non_testable_exceptions(spec_group, ntg) 112 process_non_testable_verifications(spec_group, ntg) 113 end 114 115 def process_non_testable_description(spec_group, ntg) 116 ntg['description'] && split(ntg['description']).each do |ntda| 117 spec_description = spec_group['description_tests'].find { |sd| same?(sd['assertion'], ntda) } 118 if spec_description.nil? 119 warn "Non testable description \"#{ntda}\" in group \"#{ntg['title']}\" not found in iSA." 120 else 121 spec_description['non_testable'] = true 122 end 123 end 124 end 125 126 def process_non_testable_instructions(spec_group, ntg) 127 ntg['instructions']&.each do |nti| 128 spec_instruction = spec_group['instructions'].find { |si| si['sig'] == nti['sig'] } 129 if spec_instruction.nil? 130 warn "Non testable instruction \"#{nti['sig']}\" in group \"#{ntg['title']}\" not found in ISA." 131 else 132 spec_instruction['non_testable'] = true 133 end 134 end 135 end 136 137 def process_non_testable_exceptions(spec_group, ntg) 138 ntg['exceptions']&.each do |nte| 139 spec_exception = spec_group['exceptions_tests'].find { |se| se['exception'] == nte } 140 if spec_exception.nil? 141 warn "Non testable exception \"#{nte}\" in group \"#{ntg['title']}\" not found in ISA." 142 else 143 spec_exception['non_testable'] = true 144 end 145 end 146 end 147 148 def process_non_testable_verifications(spec_group, ntg) 149 ntg['verification']&.each do |ntv| 150 spec_verification = spec_group['verification_tests'].find { |sv| sv['verification'] == ntv } 151 if spec_verification.nil? 152 warn "Non testable verification \"#{ntv}\" in group \"#{ntg['title']}\" not found in ISA." 153 else 154 spec_verification['non_testable'] = true 155 end 156 end 157 end 158 159 # split long-text description into array of assertions 160 def split(description) # rubocop:disable Metrics 161 result = [] 162 small = false 163 description.split(/\./).each do |p| 164 if small 165 result[-1] += ".#{p}" 166 small = false if p.length > 5 167 elsif p.length > 5 168 result << p.lstrip 169 else 170 if result.length.zero? 171 result << p.lstrip 172 else 173 result[-1] += ".#{p}" 174 end 175 small = true 176 end 177 end 178 result 179 end 180 181 def same?(str1, str2) 182 str1.tr('^A-Za-z0-9', '').downcase == str2.tr('^A-Za-z0-9', '').downcase 183 end 184 185 def process_test_file(testdir, file) 186 raw = read_test_data(File.join(testdir, file)) 187 tdata = YAML.safe_load(raw) 188 189 if tdata.class != Array || tdata.length.zero? 190 @orphaned << { 'file' => file, 'error' => 'Bad test format, expected array of titles', 'comment' => raw } 191 return 192 end 193 194 tdata.each do |tg| 195 process_test_data(tg, file) 196 end 197 end 198 199 def read_test_data(filename) 200 lines_array = [] 201 started = false 202 File.readlines(filename).each do |line| 203 started = true if line[0..3] == '#---' 204 lines_array << line[1..-1] if started 205 break if line[0] != '#' 206 end 207 lines_array.join("\n") 208 end 209 210 def process_test_data(test_group, file) 211 spec_group = @data['groups'].find { |g| g['title'] == test_group['title'] } 212 if spec_group.nil? 213 @orphaned << { 'file' => file, 'error' => 'Group with given title not found in the ISA', 'comment' => test_group } 214 return 215 end 216 217 assertions = proc_test_instructions(test_group, spec_group, file) + 218 proc_test_descriptions(test_group, spec_group, file) + 219 proc_test_exceptions(test_group, spec_group, file) + 220 proc_test_verifications(test_group, spec_group, file) 221 if assertions.zero? 222 @orphaned << { 'file' => file, 'error' => 'Test header doesn\'t match any assertions in ISA', 223 'comment' => test_group } 224 end 225 end 226 227 def proc_test_instructions(test_group, spec_group, file) 228 cnt = 0 229 test_group['instructions']&.each do |ti| 230 cnt += proc_test_instruction(ti, spec_group, file) 231 end 232 cnt 233 end 234 235 def proc_test_instruction(test_instr, spec_group, file) 236 gi = spec_group['instructions'].find { |x| x['sig'] == test_instr['sig'] } 237 if gi.nil? 238 @orphaned << { 'file' => file, 'error' => 'Given instruction not found in the ISA', 'comment' => test_instr } 239 return 0 240 end 241 if gi['non_testable'] 242 @orphaned << { 'file' => file, 'error' => 'Given instruction is non-testable', 'comment' => test_instr } 243 return 0 244 end 245 gi['tests'] << file 246 1 247 end 248 249 def proc_test_descriptions(test_group, spec_group, file) 250 cnt = 0 251 test_group['description'] && split(test_group['description']).each do |tda| 252 cnt += proc_test_description(tda, spec_group, file) 253 end 254 cnt 255 end 256 257 def proc_test_description(test_descr, spec_group, file) 258 sd = spec_group['description_tests']&.find { |sda| same?(sda['assertion'], test_descr) } 259 if sd.nil? 260 @orphaned << { 'file' => file, 'error' => 'Given description assertion not found in the spec', 261 'comment' => test_descr } 262 return 0 263 end 264 if sd['non_testable'] 265 @orphaned << { 'file' => file, 'error' => 'Given description is non-testable', 'comment' => test_descr } 266 return 0 267 end 268 sd['tests'] << file 269 1 270 end 271 272 def proc_test_exceptions(test_group, spec_group, file) 273 cnt = 0 274 test_group['exceptions']&.each do |te| 275 cnt += proc_test_exception(te, spec_group, file) 276 end 277 cnt 278 end 279 280 def proc_test_exception(test_exc, spec_group, file) 281 se = spec_group['exceptions_tests'].find { |x| x['exception'] == test_exc } 282 if se.nil? 283 @orphaned << { 'file' => file, 'error' => 'Given exception assertion not found in the spec', 284 'comment' => test_exc } 285 return 0 286 end 287 if se['non_testable'] 288 @orphaned << { 'file' => file, 'error' => 'Given exception assertion is non-testable', 'comment' => test_exc } 289 return 0 290 end 291 se['tests'] << file 292 1 293 end 294 295 def proc_test_verifications(test_group, spec_group, file) 296 cnt = 0 297 test_group['verification']&.each do |tv| 298 cnt += proc_test_verification(tv, spec_group, file) 299 end 300 cnt 301 end 302 303 def proc_test_verification(test_ver, spec_group, file) 304 sv = spec_group['verification_tests'].find { |x| x['verification'] == test_ver } 305 if sv.nil? 306 @orphaned << { 'file' => file, 'error' => 'Given verification assertion not found in the spec', 307 'comment' => test_ver } 308 return 0 309 end 310 if sv['non_testable'] 311 @orphaned << { 'file' => file, 'error' => 'Given verification assertion is non-testable', 'comment' => test_ver } 312 return 0 313 end 314 sv['tests'] << file 315 1 316 end 317end 318