Skip to content

Commit dcaf436

Browse files
committed
refactor: add full testing for logs:clear command
1 parent 1948aae commit dcaf436

File tree

2 files changed

+128
-22
lines changed

2 files changed

+128
-22
lines changed

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: 121 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,145 @@ 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+
parent::tearDown();
3753
}
3854

39-
protected function createDummyLogFiles(): void
55+
private function createDummyLogFiles(): void
4056
{
4157
$date = $this->date;
4258
$path = WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$date}.log";
4359

4460
// create 10 dummy log files
4561
for ($i = 0; $i < 10; $i++) {
4662
$newDate = date('Y-m-d', strtotime("+1 year -{$i} day"));
47-
$path = str_replace($date, $newDate, $path);
63+
64+
$path = str_replace($date, $newDate, $path);
4865
file_put_contents($path, 'Lorem ipsum');
4966

5067
$date = $newDate;
5168
}
5269
}
5370

54-
public function testClearLogsWorks(): void
71+
public function testClearLogsUsingForce(): void
5572
{
56-
// test clean logs dir
73+
$this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log");
74+
75+
command('logs:clear --force');
76+
5777
$this->assertFileDoesNotExist(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log");
78+
$this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . 'index.html');
79+
$this->assertSame("Logs cleared.\n", preg_replace('/\e\[[^m]+m/', '', $this->getStreamFilterBuffer()));
80+
}
5881

59-
// test dir is now populated with logs
60-
$this->createDummyLogFiles();
82+
public function testClearLogsAbortsClearWithoutForce(): void
83+
{
84+
$this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log");
85+
86+
$io = new MockInputOutput();
87+
$io->setInputs(['n']);
88+
CLI::setInputOutput($io);
89+
90+
command('logs:clear');
91+
92+
$this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log");
93+
$this->assertSame(
94+
<<<'EOT'
95+
Deleting logs aborted.
96+
If you want, use the "--force" option to force delete all log files.
97+
98+
EOT,
99+
preg_replace('/\e\[[^m]+m/', '', $io->getOutput(2) . $io->getOutput(3)),
100+
);
101+
}
102+
103+
public function testClearLogsAbortsClearWithoutForceWithDefaultAnswer(): void
104+
{
105+
$this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log");
106+
107+
$io = new MockInputOutput();
108+
$io->setInputs(['']);
109+
CLI::setInputOutput($io);
110+
111+
command('logs:clear');
112+
113+
$this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log");
114+
$this->assertSame(
115+
<<<'EOT'
116+
Deleting logs aborted.
117+
If you want, use the "--force" option to force delete all log files.
118+
119+
EOT,
120+
preg_replace('/\e\[[^m]+m/', '', $io->getOutput(2) . $io->getOutput(3)),
121+
);
122+
}
123+
124+
public function testClearLogsWithoutForceButWithConfirmation(): void
125+
{
61126
$this->assertFileExists(WRITEPATH . 'logs' . DIRECTORY_SEPARATOR . "log-{$this->date}.log");
62127

63-
command('logs:clear -force');
64-
$result = $this->getStreamFilterBuffer();
128+
$io = new MockInputOutput();
129+
$io->setInputs(['y']);
130+
CLI::setInputOutput($io);
131+
132+
command('logs:clear');
65133

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

0 commit comments

Comments
 (0)