xref: /aosp_15_r20/external/bazel-skylib/rules/diff_test.bzl (revision bcb5dc7965af6ee42bf2f21341a2ec00233a8c8a)
1# Copyright 2019 The Bazel Authors. All rights reserved.
2#
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
15"""A test rule that compares two binary files.
16
17The rule uses a Bash command (diff) on Linux/macOS/non-Windows, and a cmd.exe
18command (fc.exe) on Windows (no Bash is required).
19"""
20
21load("//lib:shell.bzl", "shell")
22
23def _runfiles_path(f):
24    if f.root.path:
25        return f.path[len(f.root.path) + 1:]  # generated file
26    else:
27        return f.path  # source file
28
29def _diff_test_impl(ctx):
30    if ctx.attr.is_windows:
31        test_bin = ctx.actions.declare_file(ctx.label.name + "-test.bat")
32        ctx.actions.write(
33            output = test_bin,
34            content = """@rem Generated by diff_test.bzl, do not edit.
35@echo off
36SETLOCAL ENABLEEXTENSIONS
37SETLOCAL ENABLEDELAYEDEXPANSION
38set MF=%RUNFILES_MANIFEST_FILE:/=\\%
39set PATH=%SYSTEMROOT%\\system32
40set F1={file1}
41set F2={file2}
42if "!F1:~0,9!" equ "external/" (set F1=!F1:~9!) else (set F1=!TEST_WORKSPACE!/!F1!)
43if "!F2:~0,9!" equ "external/" (set F2=!F2:~9!) else (set F2=!TEST_WORKSPACE!/!F2!)
44for /F "tokens=2* usebackq" %%i in (`findstr.exe /l /c:"!F1! " "%MF%"`) do (
45  set RF1=%%i
46  set RF1=!RF1:/=\\!
47)
48if "!RF1!" equ "" (
49  if "%RUNFILES_MANIFEST_ONLY%" neq "1" if exist "%RUNFILES_DIR%\\%F1%" (
50    set RF1="%RUNFILES_DIR%\\%F1%"
51  ) else (
52    if exist "{file1}" (
53      set RF1="{file1}"
54    )
55  )
56  if "!RF1!" neq "" (
57    set RF1=!RF1:/=\\!
58  ) else (
59    echo>&2 ERROR: !F1! not found
60    exit /b 1
61  )
62)
63for /F "tokens=2* usebackq" %%i in (`findstr.exe /l /c:"!F2! " "%MF%"`) do (
64  set RF2=%%i
65  set RF2=!RF2:/=\\!
66)
67if "!RF2!" equ "" (
68  if "%RUNFILES_MANIFEST_ONLY%" neq "1" if exist "%RUNFILES_DIR%\\%F2%" (
69    set RF2="%RUNFILES_DIR%\\%F2%"
70  ) else (
71    if exist "{file2}" (
72      set RF2="{file2}"
73    )
74  )
75  if "!RF2!" neq "" (
76    set RF2=!RF2:/=\\!
77  ) else (
78    echo>&2 ERROR: !F2! not found
79    exit /b 1
80  )
81)
82fc.exe 2>NUL 1>NUL /B "!RF1!" "!RF2!"
83if %ERRORLEVEL% neq 0 (
84  if %ERRORLEVEL% equ 1 (
85    echo>&2 FAIL: files "{file1}" and "{file2}" differ. {fail_msg}
86    exit /b 1
87  ) else (
88    fc.exe /B "!RF1!" "!RF2!"
89    exit /b %errorlevel%
90  )
91)
92""".format(
93                # TODO(arostovtsev): use shell.escape_for_bat when https://github.com/bazelbuild/bazel-skylib/pull/363 is merged
94                fail_msg = ctx.attr.failure_message,
95                file1 = _runfiles_path(ctx.file.file1),
96                file2 = _runfiles_path(ctx.file.file2),
97            ),
98            is_executable = True,
99        )
100    else:
101        test_bin = ctx.actions.declare_file(ctx.label.name + "-test.sh")
102        ctx.actions.write(
103            output = test_bin,
104            content = r"""#!/usr/bin/env bash
105set -euo pipefail
106F1="{file1}"
107F2="{file2}"
108[[ "$F1" =~ ^external/* ]] && F1="${{F1#external/}}" || F1="$TEST_WORKSPACE/$F1"
109[[ "$F2" =~ ^external/* ]] && F2="${{F2#external/}}" || F2="$TEST_WORKSPACE/$F2"
110if [[ -d "${{RUNFILES_DIR:-/dev/null}}" && "${{RUNFILES_MANIFEST_ONLY:-}}" != 1 ]]; then
111  RF1="$RUNFILES_DIR/$F1"
112  RF2="$RUNFILES_DIR/$F2"
113elif [[ -f "${{RUNFILES_MANIFEST_FILE:-/dev/null}}" ]]; then
114  RF1="$(grep -F -m1 "$F1 " "$RUNFILES_MANIFEST_FILE" | sed 's/^[^ ]* //')"
115  RF2="$(grep -F -m1 "$F2 " "$RUNFILES_MANIFEST_FILE" | sed 's/^[^ ]* //')"
116elif [[ -f "$TEST_SRCDIR/$F1" && -f "$TEST_SRCDIR/$F2" ]]; then
117  RF1="$TEST_SRCDIR/$F1"
118  RF2="$TEST_SRCDIR/$F2"
119else
120  echo >&2 "ERROR: could not find \"{file1}\" and \"{file2}\""
121  exit 1
122fi
123if ! diff "$RF1" "$RF2"; then
124  echo >&2 "FAIL: files \"{file1}\" and \"{file2}\" differ. "{fail_msg}
125  exit 1
126fi
127""".format(
128                fail_msg = shell.quote(ctx.attr.failure_message),
129                file1 = _runfiles_path(ctx.file.file1),
130                file2 = _runfiles_path(ctx.file.file2),
131            ),
132            is_executable = True,
133        )
134    return DefaultInfo(
135        executable = test_bin,
136        files = depset(direct = [test_bin]),
137        runfiles = ctx.runfiles(files = [test_bin, ctx.file.file1, ctx.file.file2]),
138    )
139
140_diff_test = rule(
141    attrs = {
142        "failure_message": attr.string(),
143        "file1": attr.label(
144            allow_single_file = True,
145            mandatory = True,
146        ),
147        "file2": attr.label(
148            allow_single_file = True,
149            mandatory = True,
150        ),
151        "is_windows": attr.bool(mandatory = True),
152    },
153    test = True,
154    implementation = _diff_test_impl,
155)
156
157def diff_test(name, file1, file2, failure_message = None, **kwargs):
158    """A test that compares two files.
159
160    The test succeeds if the files' contents match.
161
162    Args:
163      name: The name of the test rule.
164      file1: Label of the file to compare to `file2`.
165      file2: Label of the file to compare to `file1`.
166      failure_message: Additional message to log if the files' contents do not match.
167      **kwargs: The [common attributes for tests](https://bazel.build/reference/be/common-definitions#common-attributes-tests).
168    """
169    _diff_test(
170        name = name,
171        file1 = file1,
172        file2 = file2,
173        failure_message = failure_message,
174        is_windows = select({
175            "@bazel_tools//src/conditions:host_windows": True,
176            "//conditions:default": False,
177        }),
178        **kwargs
179    )
180