1+ <?php
2+ /**
3+ * Test Framework: PHPUnit
4+ * Purpose: Validate critical .gitignore patterns are present and consistent.
5+ */
6+ use PHPUnit \Framework \TestCase ;
7+
8+ final class GitignoreValidationTest extends TestCase
9+ {
10+ private static $ path = __DIR__ . '/../.gitignore ' ;
11+ private static $ expected = [
12+ // OS-specific
13+ '.DS_Store ' ,
14+ 'Thumbs.db ' ,
15+ 'ehthumbs.db ' ,
16+ 'Desktop.ini ' ,
17+ // Temp/log/backup
18+ '*.log ' ,
19+ '*.tmp ' ,
20+ '*.swp ' ,
21+ '*.swo ' ,
22+ '*.bak ' ,
23+ '*.orig ' ,
24+ // Secrets
25+ '.env ' ,
26+ '.env.* ' ,
27+ '*.key ' ,
28+ '*.pem ' ,
29+ // Build/cache
30+ 'dist/ ' ,
31+ 'build/ ' ,
32+ 'out/ ' ,
33+ 'tmp/ ' ,
34+ 'cache/ ' ,
35+ '*.pyc ' ,
36+ '__pycache__/ ' ,
37+ // IDE
38+ '.vscode/ ' ,
39+ '.history/ ' ,
40+ '.idea/ ' ,
41+ // Node
42+ 'node_modules/ ' ,
43+ 'npm-debug.log ' ,
44+ 'yarn-error.log ' ,
45+ '*.lock ' ,
46+ // PHP
47+ 'vendor/ ' ,
48+ 'composer.phar ' ,
49+ '.phpunit.result.cache ' ,
50+ 'phpunit.xml.dist ' ,
51+ '.php-cs-fixer.cache ' ,
52+ '.phpstan/ ' ,
53+ 'psalm.xml.dist ' ,
54+ 'storage/ ' ,
55+ 'bootstrap/cache/ ' ,
56+ 'var/ ' ,
57+ '*.cache ' ,
58+ '.env.backup ' ,
59+ '.env.local ' ,
60+ '.env.production ' ,
61+ '.env.testing ' ,
62+ 'homestead.yaml ' ,
63+ 'Homestead.yaml ' ,
64+ 'Homestead.json ' ,
65+ // WordPress
66+ 'wp-content/uploads/ ' ,
67+ 'wp-content/cache/ ' ,
68+ 'wp-content/upgrade/ ' ,
69+ 'wp-content/debug.log ' ,
70+ // Coverage
71+ 'coverage/ ' ,
72+ '*.lcov ' ,
73+ // Other binaries
74+ '*.iml ' ,
75+ '*.class ' ,
76+ '*.o ' ,
77+ '*.out ' ,
78+ '*.so ' ,
79+ '*.dll ' ,
80+ '*.exe ' ,
81+ '*.pid ' ,
82+ ];
83+
84+ private function readGitignore (): array
85+ {
86+ $ this ->assertFileExists (self ::$ path , ".gitignore must exist at repo root. " );
87+ $ content = file_get_contents (self ::$ path );
88+ $ this ->assertNotFalse ($ content , "Failed to read .gitignore " );
89+ // Normalize line endings and trim trailing spaces
90+ $ lines = preg_split ('/\R/u ' , $ content );
91+ return array_map (static function ($ l ) {
92+ return rtrim ($ l , " \t" );
93+ }, $ lines );
94+ }
95+
96+ public function test_expected_patterns_are_present (): void
97+ {
98+ $ lines = $ this ->readGitignore ();
99+ $ set = array_flip ($ lines );
100+ foreach (self ::$ expected as $ pattern ) {
101+ $ this ->assertArrayHasKey ($ pattern , $ set , "Missing required .gitignore entry: {$ pattern }" );
102+ }
103+ }
104+
105+ public function test_no_trailing_whitespace_on_rule_lines (): void
106+ {
107+ $ content = file_get_contents (self ::$ path );
108+ $ this ->assertNotFalse ($ content );
109+ $ bad = [];
110+ $ i = 0 ;
111+ foreach (preg_split ('/\R/u ' , $ content ) as $ line ) {
112+ $ i ++;
113+ if ($ line !== '' && $ line [0 ] !== '# ' ) {
114+ if (preg_match ('/[ \t]+$/ ' , $ line )) {
115+ $ bad [] = $ i ;
116+ }
117+ }
118+ }
119+ $ this ->assertCount (0 , $ bad , "Trailing whitespace detected on lines: " . implode (', ' , $ bad ));
120+ }
121+
122+ public function test_has_trailing_newline_at_eof (): void
123+ {
124+ $ content = file_get_contents (self ::$ path );
125+ $ this ->assertNotFalse ($ content );
126+ $ this ->assertTrue (substr ($ content , -1 ) === "\n" , "Expected .gitignore to end with a newline. " );
127+ }
128+
129+ public function test_no_duplicate_rules (): void
130+ {
131+ $ lines = $ this ->readGitignore ();
132+ $ counts = [];
133+ foreach ($ lines as $ line ) {
134+ if ($ line === '' || str_starts_with ($ line , '# ' )) continue ;
135+ $ counts [$ line ] = ($ counts [$ line ] ?? 0 ) + 1 ;
136+ }
137+ $ dupes = array_keys (array_filter ($ counts , fn ($ c ) => $ c > 1 ));
138+ $ this ->assertEmpty ($ dupes , "Duplicate .gitignore rules found: " . implode (', ' , $ dupes ));
139+ }
140+
141+ public function test_section_headers_exist (): void
142+ {
143+ $ lines = $ this ->readGitignore ();
144+ $ headers = [
145+ '# OS依存ファイル ' ,
146+ '# 一時ファイル・ログ・バックアップ ' ,
147+ '# 環境変数・機密情報 ' ,
148+ '# ビルド成果物・キャッシュ ' ,
149+ '# IDE / エディタ設定 ' ,
150+ '# Node.js関連 ' ,
151+ '# PHP関連設定 ' ,
152+ '# カバレッジ関連 ' ,
153+ '# その他 ' ,
154+ ];
155+ $ set = array_flip ($ lines );
156+ foreach ($ headers as $ h ) {
157+ $ this ->assertArrayHasKey ($ h , $ set , "Missing section header: {$ h }" );
158+ }
159+ }
160+ }
0 commit comments