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 indentationpartition
- splitting strings based on a substring- Using
|
to pass imports toeval()
- I don’t useeval
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)