• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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