Approach: assume pipeline does (1) tokenize -> token_ids (no special tokens), (2) add special tokens: [CLS] at index 0 and [SEP] at end, (3) truncate to max_seq_len=128, (4) pad to length 128 with PAD id 0, and (5) build attention_mask with 1 for non-PAD tokens. Below are pytest-style unit tests that validate behavior for input token lengths 0, 127, 128, 129 and check special token placement and attention masks.python
import pytest
CLS_ID = 101
SEP_ID = 102
PAD_ID = 0
MAX_LEN = 128
# hypothetical function under test:
# def prepare_sequence(token_ids, max_len=MAX_LEN) -> (input_ids, attention_mask)
# - token_ids: list of token ids WITHOUT special tokens
# - returns input_ids length == max_len, attention_mask length == max_len
def assert_basic_properties(input_ids, attention_mask, nonpad_count):
assert len(input_ids) == MAX_LEN
assert len(attention_mask) == MAX_LEN
# attention_mask sums to number of non-pad tokens
assert sum(attention_mask) == nonpad_count
# padded positions must be PAD_ID and have attention 0
for i, m in enumerate(attention_mask):
if m == 0:
assert input_ids[i] == PAD_ID
def test_length_zero():
# empty token list -> only CLS and SEP remain -> 2 tokens, rest PAD
input_ids, attn = prepare_sequence([], MAX_LEN)
assert input_ids[0] == CLS_ID
assert input_ids[1] == SEP_ID
assert input_ids[2] == PAD_ID
assert_basic_properties(input_ids, attn, nonpad_count=2)
def test_length_127():
# 127 tokens + CLS + SEP -> 129 -> must truncate to 128:
# expected behavior: truncate one token so total==128 (CLS + 126 tokens + SEP)
tokens = list(range(200, 327))[:127] # dummy token ids
input_ids, attn = prepare_sequence(tokens, MAX_LEN)
assert input_ids[0] == CLS_ID
assert input_ids[-1] == SEP_ID
# number of non-pad tokens should be 128 (full)
assert_basic_properties(input_ids, attn, nonpad_count=128)
# ensure last token before SEP is original token (truncation preserved correct count)
assert input_ids[-2] != PAD_ID
def test_length_128():
# 128 tokens + CLS + SEP -> 130 -> must truncate to 128:
# expected: keep CLS + 126 tokens + SEP (same as previous)
tokens = list(range(200, 328))[:128]
input_ids, attn = prepare_sequence(tokens, MAX_LEN)
assert input_ids[0] == CLS_ID
assert input_ids[-1] == SEP_ID
assert_basic_properties(input_ids, attn, nonpad_count=128)
def test_length_129():
# 129 tokens -> even more truncation; still result full length 128
tokens = list(range(200, 329))[:129]
input_ids, attn = prepare_sequence(tokens, MAX_LEN)
assert input_ids[0] == CLS_ID
assert input_ids[-1] == SEP_ID
assert_basic_properties(input_ids, attn, nonpad_count=128)
Off-by-one risks and how tests catch them:- Counting special tokens: If implementation forgets to reserve slots for CLS/SEP, truncation may drop a real token or a special token. Tests assert CLS at idx 0 and SEP at idx -1 to catch misplacement.- Inclusive/exclusive truncation: Whether you truncate to keep total length == max_len including special tokens. Tests for input lengths 127,128,129 verify truncation logic by checking nonpad_count is exactly 128 and that the last token before SEP is not PAD.- Padding vs attention mask mismatch: Tests assert sum(attention_mask) equals nonpad tokens and that any attention_mask==0 corresponds to PAD_ID, catching off-by-one where mask/pad lengths misalign.- Zero-length input: ensures special tokens still present and padding starts at index 2, catching errors that assume at least one token exists.These focused cases exercise boundary conditions and reveal off-by-one mistakes in counting tokens, special tokens, truncation, and mask construction.