Skip to content

Commit 176bac5

Browse files
committed
refactor: add full testing to logs:clear command
1 parent 1948aae commit 176bac5

2 files changed

Lines changed: 130 additions & 22 deletions

File tree

system/Commands/Housekeeping/ClearLogs.php

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,27 +67,22 @@ public function run(array $params)
6767
$force = array_key_exists('force', $params) || CLI::getOption('force');
6868

6969
if (! $force && CLI::prompt('Are you sure you want to delete the logs?', ['n', 'y']) === 'n') {
70-
// @codeCoverageIgnoreStart
71-
CLI::error('Deleting logs aborted.', 'light_gray', 'red');
72-
CLI::error('If you want, use the "-force" option to force delete all log files.', 'light_gray', 'red');
73-
CLI::newLine();
70+
CLI::error('Deleting logs aborted.');
71+
CLI::error('If you want, use the "--force" option to force delete all log files.');
7472

75-
return;
76-
// @codeCoverageIgnoreEnd
73+
return EXIT_ERROR;
7774
}
7875

7976
helper('filesystem');
8077

8178
if (! delete_files(WRITEPATH . 'logs', false, true)) {
82-
// @codeCoverageIgnoreStart
83-
CLI::error('Error in deleting the logs files.', 'light_gray', 'red');
84-
CLI::newLine();
79+
CLI::error('Error in deleting the logs files.');
8580

86-
return;
87-
// @codeCoverageIgnoreEnd
81+
return EXIT_ERROR;
8882
}
8983

9084
CLI::write('Logs cleared.', 'green');
91-
CLI::newLine();
85+
86+
return EXIT_SUCCESS;
9287
}
9388
}

tests/system/Commands/ClearLogsTest.php

Lines changed: 123 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313

1414
namespace CodeIgniter\Commands;
1515

16+
use CodeIgniter\CLI\CLI;
1617
use CodeIgniter\Test\CIUnitTestCase;
18+
use CodeIgniter\Test\Mock\MockInputOutput;
1719
use CodeIgniter\Test\StreamFilterTrait;
1820
use PHPUnit\Framework\Attributes\Group;
21+
use PHPUnit\Framework\Attributes\RequiresOperatingSystem;
1922

2023
/**
2124
* @internal
@@ -34,37 +37,147 @@ protected function setUp(): void
3437
// test runs on other tests may log errors since default threshold
3538
// is now 4, so set this to a safe distance
3639
$this->date = date('Y-m-d', strtotime('+1 year'));
40+
41+
command('logs:clear --force');
42+
$this->resetStreamFilterBuffer();
43+
44+
$this->createDummyLogFiles();
45+
}
46+
47+
protected function tearDown(): void
48+
{
49+
command('logs:clear --force');
50+
$this->resetStreamFilterBuffer();
51+
52+
CLI::reset();
53+
54+
parent::tearDown();
3755
}
3856

39-
protected function createDummyLogFiles(): void
57+
private function createDummyLogFiles(): void
4058
{
4159
$date = $this->date;
4260
$path = WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$date}.log";
4361

4462
// create 10 dummy log files
4563
for ($i = 0; $i < 10; $i++) {
4664
$newDate = date('Y-m-d', strtotime("+1 year -{$i} day"));
47-
$path = str_replace($date, $newDate, $path);
65+
66+
$path = str_replace($date, $newDate, $path);
4867
file_put_contents($path, 'Lorem ipsum');
4968

5069
$date = $newDate;
5170
}
5271
}
5372

54-
public function testClearLogsWorks(): void
73+
public function testClearLogsUsingForce(): void
5574
{
56-
// test clean logs dir
75+
$this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log");
76+
77+
command('logs:clear --force');
78+
5779
$this->assertFileDoesNotExist(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log");
80+
$this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . 'index.html');
81+
$this->assertSame("Logs cleared.\n", preg_replace('/\e\[[^m]+m/', '', $this->getStreamFilterBuffer()));
82+
}
83+
84+
public function testClearLogsAbortsClearWithoutForce(): void
85+
{
86+
$this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log");
87+
88+
$io = new MockInputOutput();
89+
$io->setInputs(['n']);
90+
CLI::setInputOutput($io);
91+
92+
command('logs:clear');
5893

59-
// test dir is now populated with logs
60-
$this->createDummyLogFiles();
6194
$this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log");
95+
$this->assertSame(
96+
<<<'EOT'
97+
Deleting logs aborted.
98+
If you want, use the "--force" option to force delete all log files.
99+
100+
EOT,
101+
preg_replace('/\e\[[^m]+m/', '', $io->getOutput(2) . $io->getOutput(3)),
102+
);
103+
}
62104

63-
command('logs:clear -force');
64-
$result = $this->getStreamFilterBuffer();
105+
public function testClearLogsAbortsClearWithoutForceWithDefaultAnswer(): void
106+
{
107+
$this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log");
108+
109+
$io = new MockInputOutput();
110+
$io->setInputs(['']);
111+
CLI::setInputOutput($io);
112+
113+
command('logs:clear');
114+
115+
$this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log");
116+
$this->assertSame(
117+
<<<'EOT'
118+
Deleting logs aborted.
119+
If you want, use the "--force" option to force delete all log files.
120+
121+
EOT,
122+
preg_replace('/\e\[[^m]+m/', '', $io->getOutput(2) . $io->getOutput(3)),
123+
);
124+
}
125+
126+
public function testClearLogsWithoutForceButWithConfirmation(): void
127+
{
128+
$this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log");
129+
130+
$io = new MockInputOutput();
131+
$io->setInputs(['y']);
132+
CLI::setInputOutput($io);
133+
134+
command('logs:clear');
65135

66136
$this->assertFileDoesNotExist(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log");
67-
$this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . 'index.html');
68-
$this->assertStringContainsString('Logs cleared.', $result);
137+
$this->assertSame("Logs cleared.\n", preg_replace('/\e\[[^m]+m/', '', $io->getOutput(2)));
138+
}
139+
140+
#[RequiresOperatingSystem('Darwin|Linux')]
141+
public function testClearLogsFailsOnChmodFailure(): void
142+
{
143+
$path = WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log";
144+
file_put_contents($path, 'Lorem ipsum');
145+
146+
// Attempt to make the file itself undeletable by setting the
147+
// immutable/uchg flag on supported platforms.
148+
$immutableSet = false;
149+
if (str_starts_with(PHP_OS, 'Darwin')) {
150+
@exec(sprintf('chflags uchg %s', escapeshellarg($path)), $output, $rc);
151+
$immutableSet = $rc === 0;
152+
} else {
153+
// Try chattr on Linux
154+
@exec('which chattr', $whichOut, $whichRc);
155+
if ($whichRc === 0) {
156+
@exec(sprintf('chattr +i %s', escapeshellarg($path)), $output, $rc);
157+
$immutableSet = $rc === 0;
158+
}
159+
}
160+
161+
if (! $immutableSet) {
162+
// Best-effort fallback: remove owner write bit. This may not
163+
// prevent unlink on all systems but gives some protection.
164+
chmod($path, 0400);
165+
}
166+
167+
command('logs:clear --force');
168+
169+
// Restore attributes so other tests are not affected.
170+
if ($immutableSet) {
171+
if (str_starts_with(PHP_OS, 'Darwin')) {
172+
@exec(sprintf('chflags nouchg %s', escapeshellarg($path)));
173+
} else {
174+
@exec(sprintf('chattr -i %s', escapeshellarg($path)));
175+
}
176+
} else {
177+
chmod($path, 0644);
178+
}
179+
180+
$this->assertFileExists($path);
181+
$this->assertSame("Error in deleting the logs files.\n", preg_replace('/\e\[[^m]+m/', '', $this->getStreamFilterBuffer()));
69182
}
70183
}

0 commit comments

Comments
 (0)