aboutsummaryrefslogtreecommitdiff
path: root/gcc/testsuite/lib/multiline.exp
blob: fd7affcfafdf5b1d0dc1c0fb6965d841d71652cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#   Copyright (C) 2015-2016 Free Software Foundation, Inc.

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GCC; see the file COPYING3.  If not see
# <http://www.gnu.org/licenses/>.

# Testing of multiline output

# We have pre-existing testcases like this:
#   |typedef struct _GMutex GMutex; // { dg-message "previously declared here"}
# (using "|" here to indicate the start of a line),
# generating output like this:
#   |gcc/testsuite/g++.dg/diagnostic/wrong-tag-1.C:4:16: note: 'struct _GMutex' was previously declared here
# where the location of the dg-message determines the expected line at
# which the error should be reported.
#
# To handle rich error-reporting, we want to be able to verify that we
# get output like this:
#   |gcc/testsuite/g++.dg/diagnostic/wrong-tag-1.C:4:16: note: 'struct _GMutex' was previously declared here
#   | typedef struct _GMutex GMutex; // { dg-message "previously declared here"}
#   |                ^~~~~~~
# where the compiler's first line of output is as before, but in
# which it then echoes the source lines, adding annotations.
#
# We want to be able to write testcases that verify that the
# emitted source-and-annotations are sane.
#
# A complication here is that the source lines contain comments
# containing DejaGnu directives (such as the "dg-message" above).
#
# We punt this somewhat by only matching the beginnings of lines.
# so that we can write e.g.
#   |/* { dg-begin-multiline-output "" }
#   | typedef struct _GMutex GMutex;
#   |                ^~~~~~~
#   |   { dg-end-multiline-output "" } */
# to have the testsuite verify the expected output.

############################################################################
# Global variables.
############################################################################

# This is intended to only be used from within multiline.exp.
# The line number of the last dg-begin-multiline-output directive.
set _multiline_last_beginning_line -1

# A list of
#   first-line-number, last-line-number, lines
# where each "lines" is a list of strings.
# This is cleared at the end of each test by gcc-dg.exp's wrapper for dg-test.
set multiline_expected_outputs []

############################################################################
# Exported functions.
############################################################################

# Mark the beginning of an expected multiline output
# All lines between this and the next dg-end-multiline-output are
# expected to be seen.

proc dg-begin-multiline-output { args } {
    global _multiline_last_beginning_line
    verbose "dg-begin-multiline-output: args: $args" 3
    set line [expr [lindex $args 0] + 1]
    set _multiline_last_beginning_line $line
}

# Mark the end of an expected multiline output
# All lines up to here since the last dg-begin-multiline-output are
# expected to be seen.

proc dg-end-multiline-output { args } {
    global _multiline_last_beginning_line
    verbose "dg-end-multiline-output: args: $args" 3
    set line [expr [lindex $args 0] - 1]
    verbose "multiline output lines: $_multiline_last_beginning_line-$line" 3

    upvar 1 prog prog
    verbose "prog: $prog" 3
    # "prog" now contains the filename
    # Load it and split it into lines

    set lines [_get_lines $prog $_multiline_last_beginning_line $line]

    verbose "lines: $lines" 3
    # Create an entry of the form:  first-line, last-line, lines
    set entry [list $_multiline_last_beginning_line $line $lines]
    global multiline_expected_outputs
    lappend multiline_expected_outputs $entry
    verbose "within dg-end-multiline-output: multiline_expected_outputs: $multiline_expected_outputs" 3

    set _multiline_last_beginning_line -1
}

# Hook to be called by prune.exp's prune_gcc_output to
# look for the expected multiline outputs, pruning them,
# reporting PASS for those that are found, and FAIL for
# those that weren't found.
#
# It returns a pruned version of its output.

proc handle-multiline-outputs { text } {
    global multiline_expected_outputs
    global testname_with_flags
    set index 0
    foreach entry $multiline_expected_outputs {
	verbose "  entry: $entry" 3
	set start_line [lindex $entry 0]
	set end_line   [lindex $entry 1]
	set multiline  [lindex $entry 2]
	verbose "  multiline: $multiline" 3
	set rexp [_build_multiline_regex $multiline $index]
	verbose "rexp: ${rexp}" 4
	# Escape newlines in $rexp so that we can print them in
	# pass/fail results.
	set escaped_regex [string map {"\n" "\\n"} $rexp]
	verbose "escaped_regex: ${escaped_regex}" 4

	set title "$testname_with_flags expected multiline pattern lines $start_line-$end_line"

	# Use "regsub" to attempt to prune the pattern from $text
	if {[regsub -line $rexp $text "" text]} {
	    # Success; the multiline pattern was pruned.
	    pass "$title was found: \"$escaped_regex\""
	} else {
	    fail "$title not found: \"$escaped_regex\""
	}

	set index [expr $index + 1]
    }

    return $text
}

############################################################################
# Internal functions
############################################################################

# Load FILENAME and extract the lines from FIRST_LINE
# to LAST_LINE (inclusive) as a list of strings.

proc _get_lines { filename first_line last_line } {
    verbose "_get_lines" 3
    verbose "  filename: $filename" 3
    verbose "  first_line: $first_line" 3
    verbose "  last_line: $last_line" 3

    set fp [open $filename r]
    set file_data [read $fp]
    close $fp
    set data [split $file_data "\n"]
    set linenum 1
    set lines []
    foreach line $data {
	verbose "line $linenum: $line" 4
	if { $linenum >= $first_line && $linenum <= $last_line } {
	    lappend lines $line
	}
	set linenum [expr $linenum + 1]
    }

    return $lines
}

# Convert $multiline from a list of strings to a multiline regex
# We need to support matching arbitrary followup text on each line,
# to deal with comments containing containing DejaGnu directives.

proc _build_multiline_regex { multiline index } {
    verbose "_build_multiline_regex: $multiline $index" 4

    set rexp ""
    foreach line $multiline {
	verbose "  line: $line" 4

	# We need to escape "^" and other regexp metacharacters.
	set line [string map {"^" "\\^"
	                      "(" "\\("
	                      ")" "\\)"
	                      "[" "\\["
	                      "]" "\\]"
	                      "{" "\\{"
	                      "}" "\\}"
	                      "." "\\."
	                      "\\" "\\\\"
	                      "?" "\\?"
	                      "+" "\\+"
	                      "*" "\\*"
	                      "|" "\\|"} $line]

	append rexp $line
	if {[string match "*^" $line] || [string match "*~" $line]} {
	    # Assume a line containing a caret/range.  This must be
	    # an exact match.
	} elseif {[string match "*\\|" $line]} {
	    # Assume a source line with a right-margin.  Support
	    # arbitrary text in place of any whitespace before the
	    # right-margin, to deal with comments containing containing
	    # DejaGnu directives.

	    # Remove final "\|":
	    set rexp [string range $rexp 0 [expr [string length $rexp] - 3]]

	    # Trim off trailing whitespace:
	    set old_length [string length $rexp]
	    set rexp [string trimright $rexp]
	    set new_length [string length $rexp]

	    # Replace the trimmed whitespace with "." chars to match anything:
	    set ws [string repeat "." [expr $old_length - $new_length]]
	    set rexp "${rexp}${ws}"

	    # Add back the trailing '\|':
	    set rexp "${rexp}\\|"
	} else {
	    # Assume that we have a quoted source line.
	    # Support arbitrary followup text on each line,
	    # to deal with comments containing containing DejaGnu
	    # directives.
	    append rexp ".*"
	}
	append rexp "\n"
    }

    # dg.exp's dg-test trims leading whitespace from the output
    # in this line:
    #   set comp_output [string trimleft $comp_output]
    # so we can't rely on the exact leading whitespace for the
    # first line in the *first* multiline regex.
    #
    # Trim leading whitespace from the regexp, replacing it with
    # a "\s*", to match zero or more whitespace characters.
    if { $index == 0 } {
	set rexp [string trimleft $rexp]
	set rexp "\\s*$rexp"
    }

    verbose "rexp: $rexp" 4

    return $rexp
}