1#   Copyright (C) 1997-2020 Free Software Foundation, Inc.
2
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with GCC; see the file COPYING3.  If not see
15# <http://www.gnu.org/licenses/>.
16
17# Verify various kinds of gcov output: line counts, branch percentages,
18# and call return percentages.  None of this is language-specific.
19
20global GCOV
21
22#
23# clean-gcov-file -- delete a working file the compiler creates for gcov
24#
25# TESTCASE is the name of the test.
26# SUFFIX is file suffix
27
28proc clean-gcov-file { testcase suffix } {
29    set basename [file tail $testcase]
30    set base [file rootname $basename]
31    remote_file host delete $base.$suffix
32}
33
34#
35# clean-gcov -- delete the working files the compiler creates for gcov
36#
37# TESTCASE is the name of the test.
38#
39proc clean-gcov { testcase } {
40    clean-gcov-file $testcase "gcno"
41    clean-gcov-file $testcase "gcda"
42    clean-gcov-file $testcase "gcov"
43    clean-gcov-file $testcase "h.gcov"
44}
45
46#
47# verify-lines -- check that line counts are as expected
48#
49# TESTNAME is the name of the test, including unique flags.
50# TESTCASE is the name of the test file.
51# FILE is the name of the gcov output file.
52#
53proc verify-lines { testname testcase file } {
54    #send_user "verify-lines\n"
55    global subdir
56
57    set failed 0
58    set fd [open $file r]
59    while { [gets $fd line] >= 0 } {
60        # We want to match both "-" and "#####" as count as well as numbers,
61        # since we want to detect lines that shouldn't be marked as covered.
62	if [regexp "^ *(\[^:]*): *(\[0-9\\-#]+):.*count\\((\[0-9\\-#=\\.kMGTPEZY\*]+)\\)(.*)" \
63		"$line" all is n shouldbe rest] {
64	    if [regexp "^ *{(.*)}" $rest all xfailed] {
65		switch [dg-process-target $xfailed] {
66		    "N" { continue }
67		    "F" { setup_xfail "*-*-*" }
68		}
69	    }
70	    if { $is == "" } {
71		fail "$testname line $n: no data available"
72		incr failed
73	    } elseif { $is != $shouldbe } {
74		fail "$testname line $n: is $is:should be $shouldbe"
75		incr failed
76	    } else {
77		pass "$testname count for line $n"
78	    }
79	}
80    }
81    close $fd
82    return $failed
83}
84
85
86#
87# verify-branches -- check that branch percentages are as expected
88#
89# TESTNAME is the name of the test, including unique flags.
90# TESTCASE is the name of the test file.
91# FILE is the name of the gcov output file.
92#
93# Checks are based on comments in the source file.  This means to look for
94# branch percentages 10 or 90, 20 or 80, and # 70 or 30:
95#     /* branch(10, 20, 70) */
96# This means that all specified percentages should have been seen by now:
97#     /* branch(end) */
98# All specified percentages must also be seen by the next branch(n) or
99# by the end of the file.
100#
101# Each check depends on the compiler having generated the expected
102# branch instructions.  Don't check for branches that might be
103# optimized away or replaced with predicated instructions.
104#
105proc verify-branches { testname testcase file } {
106    #send_user "verify-branches\n"
107
108    set failed 0
109    set shouldbe ""
110    set fd [open $file r]
111    set n 0
112    while { [gets $fd line] >= 0 } {
113	regexp "^\[^:\]+: *(\[0-9\]+):" "$line" all n
114	if [regexp "branch" $line] {
115	    verbose "Processing branch line $n: $line" 3
116	    if [regexp "branch\\((\[0-9 \]+)\\)" "$line" all new_shouldbe] {
117		# All percentages in the current list should have been seen.
118		if {[llength $shouldbe] != 0} {
119		    fail "$testname line $n: expected branch percentages not found: $shouldbe"
120		    incr failed
121		    set shouldbe ""
122		}
123		set shouldbe $new_shouldbe
124		#send_user "$n: looking for: $shouldbe\n"
125	        # Record the percentages to check for. Replace percentage
126		# n > 50 with 100-n, since block ordering affects the
127		# direction of a branch.
128		for {set i 0} {$i < [llength $shouldbe]} {incr i} {
129		    set num [lindex $shouldbe $i]
130		    if {$num > 50} {
131			set shouldbe [lreplace $shouldbe $i $i [expr 100 - $num]]
132		    }
133		}
134	    } elseif [regexp "branch +\[0-9\]+ taken (-\[0-9\]+)%" "$line" \
135			all taken] {
136		# Percentages should never be negative.
137		fail "$testname line $n: negative percentage: $taken"
138		incr failed
139	    } elseif [regexp "branch +\[0-9\]+ taken (\[0-9\]+)%" "$line" \
140			all taken] {
141		#send_user "$n: taken = $taken\n"
142		# Percentages should never be greater than 100.
143		if {$taken > 100} {
144		    fail "$testname line $n: branch percentage greater than 100: $taken"
145		    incr failed
146		}
147		if {$taken > 50} {
148		    set taken [expr 100 - $taken]
149		}
150		# If this percentage is one to check for then remove it
151		# from the list.  It's normal to ignore some reports.
152		set i [lsearch $shouldbe $taken]
153		if {$i != -1} {
154		    set shouldbe [lreplace $shouldbe $i $i]
155		}
156	    } elseif [regexp "branch\\(end\\)" "$line"] {
157		# All percentages in the list should have been seen by now.
158		if {[llength $shouldbe] != 0} {
159		    fail "$testname line n: expected branch percentages not found: $shouldbe"
160		    incr failed
161		}
162		set shouldbe ""
163	    }
164	}
165    }
166    # All percentages in the list should have been seen.
167    if {[llength $shouldbe] != 0} {
168	fail "$testname line $n: expected branch percentages not found: $shouldbe"
169	incr failed
170    }
171    close $fd
172    return $failed
173}
174
175#
176# verify-calls -- check that call return percentages are as expected
177#
178# TESTNAME is the name of the test, including unique flags.
179# TESTCASE is the name of the test file.
180# FILE is the name of the gcov output file.
181#
182# Checks are based on comments in the source file.  This means to look for
183# call return percentages 50, 20, 33:
184#     /* returns(50, 20, 33) */
185# This means that all specified percentages should have been seen by now:
186#     /* returns(end) */
187# All specified percentages must also be seen by the next returns(n) or
188# by the end of the file.
189#
190# Each check depends on the compiler having generated the expected
191# call instructions.  Don't check for calls that are inserted by the
192# compiler or that might be inlined.
193#
194proc verify-calls { testname testcase file } {
195    #send_user "verify-calls\n"
196
197    set failed 0
198    set shouldbe ""
199    set fd [open $file r]
200    set n 0
201    while { [gets $fd line] >= 0 } {
202	regexp "^\[^:\]+: *(\[0-9\]+):" "$line" all n
203	if [regexp "return" $line] {
204	    verbose "Processing returns line $n: $line" 3
205	    if [regexp "returns\\((\[0-9 \]+)\\)" "$line" all new_shouldbe] {
206		# All percentages in the current list should have been seen.
207		if {[llength $shouldbe] != 0} {
208		    fail "$testname line $n: expected return percentages not found: $shouldbe"
209		    incr failed
210		    set shouldbe ""
211		}
212	        # Record the percentages to check for.
213		set shouldbe $new_shouldbe
214	    } elseif [regexp "call +\[0-9\]+ returned (-\[0-9\]+)%" "$line" \
215			all returns] {
216		# Percentages should never be negative.
217		fail "$testname line $n: negative percentage: $returns"
218		incr failed
219	    } elseif [regexp "call +\[0-9\]+ returned (\[0-9\]+)%" "$line" \
220			all returns] {
221		# For branches we check that percentages are not greater than
222		# 100 but call return percentages can be, as for setjmp(), so
223		# don't count that as an error.
224		#
225		# If this percentage is one to check for then remove it
226		# from the list.  It's normal to ignore some reports.
227		set i [lsearch $shouldbe $returns]
228		if {$i != -1} {
229		    set shouldbe [lreplace $shouldbe $i $i]
230		}
231	    } elseif [regexp "returns\\(end\\)" "$line"] {
232		# All percentages in the list should have been seen by now.
233		if {[llength $shouldbe] != 0} {
234		    fail "$testname line $n: expected return percentages not found: $shouldbe"
235		    incr failed
236		}
237		set shouldbe ""
238	    }
239	}
240    }
241    # All percentages in the list should have been seen.
242    if {[llength $shouldbe] != 0} {
243	fail "$testname line $n: expected return percentages not found: $shouldbe"
244	incr failed
245    }
246    close $fd
247    return $failed
248}
249
250# Called by dg-final to run gcov and analyze the results.
251#
252# ARGS consists of the optional strings "branches" and/or "calls",
253# (indicating that these things should be verified) followed by a
254# list of arguments to provide to gcov, including the name of the
255# source file.
256
257proc run-gcov { args } {
258    global GCOV
259    global srcdir subdir
260
261    set gcov_args ""
262    set gcov_verify_calls 0
263    set gcov_verify_branches 0
264    set gcov_verify_lines 1
265    set gcov_verify_intermediate 0
266    set gcov_remove_gcda 0
267    set xfailed 0
268
269    foreach a $args {
270	if { $a == "calls" } {
271	  set gcov_verify_calls 1
272	} elseif { $a == "branches" } {
273	  set gcov_verify_branches 1
274	} elseif { $a == "intermediate" } {
275	  set gcov_verify_intermediate 1
276	  set gcov_verify_calls 0
277	  set gcov_verify_branches 0
278	  set gcov_verify_lines 0
279	} elseif { $a == "remove-gcda" } {
280	  set gcov_remove_gcda 1
281	} elseif { $gcov_args == "" } {
282	    set gcov_args $a
283	} else {
284	    switch [dg-process-target $a] {
285		"N" { return }
286		"F" { set xfailed 1 }
287	    }
288	}
289    }
290
291    set testname [testname-for-summary]
292
293    # Extract the test file name from the arguments.
294    set testcase [lindex $gcov_args end]
295
296    if { $gcov_remove_gcda } {
297	verbose "Removing $testcase.gcda"
298	clean-gcov-file $testcase "gcda"
299    }
300
301    verbose "Running $GCOV $testcase" 2
302    set testcase [remote_download host $testcase]
303    set result [remote_exec host $GCOV $gcov_args]
304    if { [lindex $result 0] != 0 } {
305	if { $xfailed } {
306	    setup_xfail "*-*-*"
307	}
308	fail "$testname gcov failed: [lindex $result 1]"
309	clean-gcov $testcase
310	return
311    }
312
313    set builtin_index [string first "File '<built-in>'" $result]
314    if { $builtin_index != -1 } {
315        fail "$testname gcov failed: <built-in>.gcov should not be created"
316        clean-gcov $testcase
317        return
318    }
319
320    # Get the gcov output file after making sure it exists.
321    set files [glob -nocomplain $testcase.gcov]
322    if { $files == "" } {
323	if { $xfailed } {
324	    setup_xfail "*-*-*"
325	}
326        fail "$testname gcov failed: $testcase.gcov does not exist"
327        clean-gcov $testcase
328        return
329    }
330    remote_upload host $testcase.gcov $testcase.gcov
331
332    # Check that line execution counts are as expected.
333    if { $gcov_verify_lines } {
334	# Check that line execution counts are as expected.
335	set lfailed [verify-lines $testname $testcase $testcase.gcov]
336    } else {
337	set lfailed 0
338    }
339
340    # If requested via the .x file, check that branch and call information
341    # is correct.
342    if { $gcov_verify_branches } {
343	set bfailed [verify-branches $testname $testcase $testcase.gcov]
344    } else {
345	set bfailed 0
346    }
347    if { $gcov_verify_calls } {
348	set cfailed [verify-calls $testname $testcase $testcase.gcov]
349    } else {
350	set cfailed 0
351    }
352    if { $gcov_verify_intermediate } {
353	# Check that intermediate format has the expected format
354	set ifailed [verify-intermediate $testname $testcase $testcase.gcov]
355    } else {
356	set ifailed 0
357    }
358
359    # Report whether the gcov test passed or failed.  If there were
360    # multiple failures then the message is a summary.
361    set tfailed [expr $lfailed + $bfailed + $cfailed + $ifailed]
362    if { $xfailed } {
363	setup_xfail "*-*-*"
364    }
365    if { $tfailed > 0 } {
366	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cfailed in return percentages, $ifailed in intermediate format"
367	if { $xfailed } {
368	    clean-gcov $testcase
369	}
370    } else {
371	pass "$testname gcov"
372	clean-gcov $testcase
373    }
374}
375