def METHOD_NAME(ctx):
    if ctx.attr.expect < 0:
        fail("ERROR: rule_size_test.expect must be positive")
    if ctx.attr.margin < 0 or ctx.attr.margin > 100:
        # Do not allow more than 100% change in size.
        fail("ERROR: rule_size_test.margin must be in range [0..100]")
    if ctx.attr.expect == 0 and ctx.attr.margin != 0:
        # Allow no margin when expecting 0 files, to avoid division by zero.
        fail("ERROR: rule_size_test.margin must be 0 when " +
             "rule_size_test.expect is 0")
    amount = len(ctx.attr.src[DefaultInfo].files.to_list())
    if ctx.attr.margin > 0:
        if amount >= ctx.attr.expect:
            diff = amount - ctx.attr.expect
        else:
            diff = ctx.attr.expect - amount
        if ((diff * 100) // ctx.attr.expect) > ctx.attr.margin:
            fail(("ERROR: rule_size_test: expected %d file(s) within %d%% " +
                  "error margin, got %d file(s) (%d%% difference)") % (
                ctx.attr.expect,
                ctx.attr.margin,
                amount,
                (diff * 100) // ctx.attr.expect,
            ))
    elif amount != ctx.attr.expect:
        fail(("ERROR: rule_size_test: expected exactly %d file(s), got %d " +
              "file(s)") % (ctx.attr.expect, amount))
    if ctx.attr.is_windows:
        test_bin = ctx.actions.declare_file(ctx.label.name + ".bat")
        # CreateProcessW can launch .bat files directly as long as they are NOT
        # empty. Therefore we write a .bat file with a comment in it.
        ctx.actions.write(
            output = test_bin,
            content = "@REM dummy",
            is_executable = True,
        )
    else:
        test_bin = ctx.actions.declare_file(ctx.label.name + ".sh")
        ctx.actions.write(
            output = test_bin,
            content = "#!/bin/sh",
            is_executable = True,
        )
    return [DefaultInfo(executable = test_bin)]