I noticed a fun post by Ned Batchelder called Testing some tidbits.

The post looks at different ways to see if a string has only 0 or 1 in it.

He posted a few ways on Bluesky/Mastodon and got a bunch of replies with more ways. And then wrote a small script to check to see if they worked.

It’s a fun post, and from it I learned about:

  • cleandoc - a way to strip leading blank space and maintain code block indentation
  • partition - splitting strings based on a substring
  • Using | to pass imports to eval() - I don’t use eval much.

I was a little dissapointed that with a title like “Testing some tidbits”, Ned didn’t use pytest or any other test framework.

Let’s fix that.
Here’s a way to test all good and bad input against all code samples.

import pytest
import re
from collections import Counter
from inspect import cleandoc

GOOD = [
    "",
    "0",
    "1",
    "000000000000000000",
    "111111111111111111",
    "101000100011110101010000101010101001001010101",
]

BAD = [
    "x",
    "nedbat",
    "x000000000000000000000000000000000000",
    "111111111111111111111111111111111111x",
    "".join(chr(i) for i in range(10000)),
]

TESTS = """
    # The original checks
    all(c in "01" for c in s)
    set(s).issubset({"0", "1"})
    set(s) <= {"0", "1"}
    re.fullmatch(r"[01]*", s)
    s.strip("01") == ""
    not s.strip("01")

    # Using min/max
    "0" <= min(s or "0") <= max(s or "1") <= "1"
    not s or (min(s) in "01" and max(s) in "01")
    ((ss := sorted(s or "0")) and ss[0] in "01" and ss[-1] in "01")

    # Using counting
    s.count("0") + s.count("1") == len(s)
    (not (ctr := Counter(s)) or (ctr["0"] + ctr["1"] == len(s)))

    # Using numeric tests
    all(97*c - c*c > 2351 for c in s.encode())
    max((abs(ord(c) - 48.5) for c in "0"+s)) < 1
    all(map(lambda x: (ord(x) ^ 48) < 2, s))

    # Removing all the 0 and 1
    re.sub(r"[01]", "", s) == ""
    len((s).translate(str.maketrans("", "", "01"))) == 0
    len((s).replace("0", "").replace("1", "")) == 0
    "".join(("1".join((s).split("0"))).split("1")) == ""

    # A few more for good measure
    set(s + "01") == set("01")
    not (set(s) - set("01"))
    not any(filter(lambda x: x not in {"0", "1"}, s))
    all(map(lambda x: x in "01", s))
"""


def clean_and_strip_comments(s):
    # cleandoc: Used to strip leading whitespace
    # Could probably just used strip() here, but going with what Ned had
    # also, I frequently use textwrap.dedent for removing leading whitespace, 
    # so I'm grateful to learn about cleandoc
    for line in cleandoc(s).splitlines():
        # partition: we just want the part before the comment
        actual_line = line.partition("#")[0]
        # throw away blank lines or lines with only comments
        if actual_line:
            yield str(actual_line)


def evaluate_code(code, input):
    # some of the code uses re and Counter
    # so we have to eval the code with these in the global namespace
    g = { "re": re, "Counter": Counter}
    return eval(code, {"s": input} | g)


def idfn(val):
    # Ned has one crazy input with 1000 characters. 
    # I'd prefer not to print all 1000 characters if someone runs pytest with -v
    return str(val)[:20]


@pytest.mark.parametrize('input', GOOD, ids=idfn)   
@pytest.mark.parametrize('code', clean_and_strip_comments(TESTS), ids=idfn)
def test_good_cases(code, input):
    assert evaluate_code(code, input)


@pytest.mark.parametrize('input', BAD, ids=idfn)
@pytest.mark.parametrize('code', clean_and_strip_comments(TESTS),  ids=idfn)
def test_bad_cases(code, input):
    assert not evaluate_code(code, input)