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