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