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