Skip to content

Commit 8c880d9

Browse files
authored
Merge pull request #1256 from PyThaiNLP/copilot/increase-test-coverage
Increase test coverage for core utilities and misspell modules
2 parents 164dbc0 + 5e582c1 commit 8c880d9

6 files changed

Lines changed: 192 additions & 8 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ full = [
226226
"sefr_cut>=1.1",
227227
"symspellpy==6.9.0",
228228
"thai-nner==0.3",
229-
"tltk>=1.6.8,<2",
229+
"tltk>=1.10,<2",
230230
"torch>=1.13.1,<3",
231231
"transformers==4.57.6",
232232
"ufal.chu-liu-edmonds==1.0.3",

tests/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ supported Python versions and operating systems:
2222

2323
The CI/CD test workflow is at
2424
<https://github.com/PyThaiNLP/pythainlp/blob/dev/.github/workflows/unittest.yml>.
25-
25+
2626
## Core tests (test_*.py)
2727

2828
- Run `unittest tests.core`
@@ -65,7 +65,7 @@ The CI/CD test workflow is at
6565
A comprehensive test suite within core tests that tests edge cases important
6666
for real-world usage:
6767

68-
- Empty strings and various whitespace handling (spaces, tabs, unicode spaces)
68+
- Empty strings and various whitespace handling (spaces, tabs, Unicode spaces)
6969
- Special characters from encoding issues, BOM, terminal copy/paste
7070
- Truncated/malformed Unicode and surrogate pairs
7171
- Emoji and modern Unicode sequences (ZWJ, modifiers, flags)

tests/compact/testc_tools.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66

77
import numpy as np
88

9-
from pythainlp.tools.misspell import misspell
9+
from pythainlp.tools.misspell import (
10+
find_misspell_candidates,
11+
misspell,
12+
search_location_of_character,
13+
)
1014

1115

1216
def _count_difference(st1: str, st2: str) -> int:
@@ -85,3 +89,58 @@ def test_misspell_with_ratio_100_percent(self):
8589
2,
8690
f"expect len(text)-2 misspells with ratio=1.5. (Δ={diff})",
8791
)
92+
93+
def test_search_location_of_character(self):
94+
"""Test search_location_of_character function."""
95+
# Test Thai characters
96+
loc = search_location_of_character("ก")
97+
self.assertIsNotNone(loc)
98+
self.assertEqual(len(loc), 4) # (language_ix, is_shift, row, pos)
99+
100+
# Test English characters
101+
loc = search_location_of_character("a")
102+
self.assertIsNotNone(loc)
103+
self.assertEqual(len(loc), 4)
104+
105+
# Test shifted characters
106+
loc = search_location_of_character("A")
107+
self.assertIsNotNone(loc)
108+
109+
# Test numbers
110+
loc = search_location_of_character("1")
111+
self.assertIsNotNone(loc)
112+
113+
# Test character not in keyboard
114+
loc = search_location_of_character("€")
115+
self.assertIsNone(loc)
116+
117+
# Test empty string
118+
# Note: Empty string returns a location because Python's "in" operator
119+
# matches empty string at the beginning of any string
120+
loc = search_location_of_character("")
121+
self.assertIsNotNone(loc)
122+
123+
def test_find_misspell_candidates(self):
124+
"""Test find_misspell_candidates function."""
125+
# Test Thai character
126+
candidates = find_misspell_candidates("ก")
127+
self.assertIsNotNone(candidates)
128+
self.assertIsInstance(candidates, list)
129+
self.assertGreater(len(candidates), 0)
130+
131+
# Test English character
132+
candidates = find_misspell_candidates("a")
133+
self.assertIsNotNone(candidates)
134+
self.assertIsInstance(candidates, list)
135+
self.assertGreater(len(candidates), 0)
136+
137+
# Test character not in keyboard
138+
candidates = find_misspell_candidates("€")
139+
self.assertIsNone(candidates)
140+
141+
# Test that candidates are different from input
142+
candidates = find_misspell_candidates("ด")
143+
if candidates:
144+
for candidate in candidates:
145+
# Candidates should be strings
146+
self.assertIsInstance(candidate, str)

tests/core/test_ancient.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ def test_aksonhan_to_current(self):
2323
self.assertEqual(aksonhan_to_current("บงงคบบ"), "บังคับ")
2424
self.assertEqual(aksonhan_to_current("สรรเพชญ"), "สรรเพชญ")
2525

26+
# Edge cases
27+
self.assertEqual(aksonhan_to_current(""), "") # empty string
28+
self.assertEqual(aksonhan_to_current("ก"), "ก") # single char
29+
self.assertEqual(aksonhan_to_current("กา"), "กา") # two chars
30+
2631
def test_convert_currency(self):
2732
self.assertEqual(
2833
convert_currency(80, "บาท")["ตำลึง"],
@@ -44,3 +49,24 @@ def test_convert_currency(self):
4449
convert_currency(1,"ชั่ง")["ชั่ง"],
4550
1.0
4651
)
52+
53+
# Test all supported units
54+
result = convert_currency(1, "บาท")
55+
self.assertIn("เบี้ย", result)
56+
self.assertIn("อัฐ", result)
57+
self.assertIn("ไพ", result)
58+
self.assertIn("เฟื้อง", result)
59+
self.assertIn("สลึง", result)
60+
self.assertIn("ตำลึง", result)
61+
62+
# Test with zero value
63+
result = convert_currency(0, "บาท")
64+
self.assertEqual(result["บาท"], 0.0)
65+
66+
# Test with fractional value
67+
result = convert_currency(0.5, "บาท")
68+
self.assertEqual(result["บาท"], 0.5)
69+
70+
# Test invalid unit
71+
with self.assertRaises(NotImplementedError):
72+
convert_currency(1, "invalid_unit")

tests/core/test_tools.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
import os
66
import tempfile
77
import unittest
8+
import warnings
89

910
from pythainlp.tools import (
1011
get_full_data_path,
1112
get_pythainlp_data_path,
1213
get_pythainlp_path,
1314
)
15+
from pythainlp.tools.core import safe_print, warn_deprecation
1416

1517

1618
class ToolsTestCase(unittest.TestCase):
@@ -57,3 +59,69 @@ def test_custom_data_dir(self):
5759
os.environ["PYTHAINLP_DATA_DIR"] = original_value
5860
elif "PYTHAINLP_DATA_DIR" in os.environ:
5961
del os.environ["PYTHAINLP_DATA_DIR"]
62+
63+
def test_warn_deprecation(self):
64+
"""Test deprecation warning function."""
65+
# Test basic deprecation warning
66+
with warnings.catch_warnings(record=True) as w:
67+
warnings.simplefilter("always")
68+
warn_deprecation("old_func")
69+
self.assertEqual(len(w), 1)
70+
self.assertTrue(issubclass(w[0].category, DeprecationWarning))
71+
self.assertIn("old_func", str(w[0].message))
72+
self.assertIn("deprecated", str(w[0].message))
73+
74+
# Test with replacement function
75+
with warnings.catch_warnings(record=True) as w:
76+
warnings.simplefilter("always")
77+
warn_deprecation("old_func", replacing_func="new_func")
78+
self.assertEqual(len(w), 1)
79+
self.assertIn("old_func", str(w[0].message))
80+
self.assertIn("new_func", str(w[0].message))
81+
82+
# Test with version information
83+
with warnings.catch_warnings(record=True) as w:
84+
warnings.simplefilter("always")
85+
warn_deprecation(
86+
"old_func",
87+
deprecated_version="1.0",
88+
removal_version="2.0"
89+
)
90+
self.assertEqual(len(w), 1)
91+
self.assertIn("1.0", str(w[0].message))
92+
self.assertIn("2.0", str(w[0].message))
93+
94+
# Test with all parameters
95+
with warnings.catch_warnings(record=True) as w:
96+
warnings.simplefilter("always")
97+
warn_deprecation(
98+
"old_func",
99+
replacing_func="new_func",
100+
deprecated_version="1.0",
101+
removal_version="2.0"
102+
)
103+
self.assertEqual(len(w), 1)
104+
message = str(w[0].message)
105+
self.assertIn("old_func", message)
106+
self.assertIn("new_func", message)
107+
self.assertIn("1.0", message)
108+
self.assertIn("2.0", message)
109+
110+
def test_safe_print(self):
111+
"""Test safe_print function."""
112+
# Test normal printing
113+
safe_print("Hello, World!")
114+
safe_print("สวัสดีครับ")
115+
116+
# Test with Unicode characters
117+
safe_print("Hello 👋 World")
118+
safe_print("ภาษาไทย")
119+
120+
# Test with empty string
121+
safe_print("")
122+
123+
# Test with special characters
124+
safe_print("\n\t")
125+
126+
# Note: Testing actual UnicodeEncodeError is environment-dependent
127+
# and would require mocking sys.stdout.encoding

tests/core/test_util.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ def test_collate(self):
9797
collate(["ไก่", "เป็ด", "หมู", "วัว"], reverse=True),
9898
["หมู", "วัว", "เป็ด", "ไก่"],
9999
)
100+
# Edge cases: mixed Thai and numbers
101+
self.assertEqual(collate(["ก", "1", "ข"]), ["1", "ก", "ข"])
102+
# Edge cases: with spaces (spaces sort before letters)
103+
result = collate([" ก", "ก", " ก"])
104+
self.assertEqual(len(result), 3)
105+
self.assertIn(" ก", result)
106+
self.assertIn("ก", result)
107+
self.assertIn(" ก", result)
100108

101109
# ### pythainlp.util.numtoword
102110

@@ -720,6 +728,11 @@ def test_emoji_to_thai(self):
720728
":ธง_ไทย: นี่คือธงประเทศไทย",
721729
)
722730

731+
# Edge cases
732+
self.assertEqual(emoji_to_thai(""), "") # empty string
733+
self.assertEqual(emoji_to_thai("no emoji"), "no emoji") # no emoji
734+
self.assertEqual(emoji_to_thai("ไม่มีอีโมจิ"), "ไม่มีอีโมจิ") # Thai no emoji
735+
723736
def test_sound_syllable(self):
724737
test = [
725738
("มา", "live"),
@@ -863,6 +876,10 @@ def test_syllable_open_close_detector(self):
863876

864877
def test_to_idna(self):
865878
self.assertEqual(to_idna("คนละครึ่ง.com"), "xn--42caj4e6bk1f5b1j.com")
879+
# Additional test cases for IDNA encoding
880+
self.assertEqual(to_idna("ไทย.com"), "xn--o3cw4h.com")
881+
self.assertEqual(to_idna("example.com"), "example.com") # ASCII unchanged
882+
self.assertEqual(to_idna("ภาษาไทย.th"), "xn--o3crh0a8bb0k.th")
866883

867884
def test_thai_strptime(self):
868885
self.assertIsNotNone(
@@ -923,6 +940,11 @@ def test_tis620_to_utf8(self):
923940
self.assertEqual(
924941
tis620_to_utf8("¡ÃзÃÇ§ÍØµÊÒË¡ÃÃÁ"), "กระทรวงอุตสาหกรรม"
925942
)
943+
# Additional test cases
944+
self.assertEqual(tis620_to_utf8("»ÃÐà·Èä·Â"), "ประเทศไทย")
945+
self.assertEqual(tis620_to_utf8("ÀÒÉÒä·Â"), "ภาษาไทย")
946+
# Empty string
947+
self.assertEqual(tis620_to_utf8(""), "")
926948

927949
def test_remove_repeat_consonants(self):
928950
# update of pythainlp.copus.thai_words() able to break this
@@ -988,11 +1010,20 @@ def test_longest_common_subsequence(self):
9881010
self.assertEqual(longest_common_subsequence("ABCBDAB", "BDCAB"), "BDAB")
9891011
self.assertEqual(longest_common_subsequence("AGGTAB", "GXTXAYB"), "GTAB")
9901012
self.assertEqual(longest_common_subsequence("ABCDGH", "AEDFHR"), "ADH")
1013+
1014+
# Edge cases
1015+
self.assertEqual(longest_common_subsequence("", ""), "") # empty strings
1016+
self.assertEqual(longest_common_subsequence("ABC", ""), "") # one empty
1017+
self.assertEqual(longest_common_subsequence("", "ABC"), "") # other empty
1018+
self.assertEqual(longest_common_subsequence("A", "A"), "A") # single char match
1019+
self.assertEqual(longest_common_subsequence("A", "B"), "") # single char no match
1020+
self.assertEqual(longest_common_subsequence("ABC", "ABC"), "ABC") # identical
1021+
self.assertEqual(longest_common_subsequence("ABC", "XYZ"), "") # no common chars
9911022
self.assertEqual(longest_common_subsequence("ABC", "AC"), "AC")
992-
self.assertEqual(longest_common_subsequence("ABC", "DEF"), "")
993-
self.assertEqual(longest_common_subsequence("", "ABC"), "")
994-
self.assertEqual(longest_common_subsequence("ABC", ""), "")
995-
self.assertEqual(longest_common_subsequence("", ""), "")
1023+
1024+
# Thai text
1025+
self.assertEqual(longest_common_subsequence("ไทย", "ไทย"), "ไทย")
1026+
self.assertEqual(longest_common_subsequence("ภาษาไทย", "ไทย"), "ไทย")
9961027

9971028
def test_analyze_thai_text(self):
9981029
self.assertEqual(

0 commit comments

Comments
 (0)