Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
#[PreserveGlobalState(false)]
class AdminGuiReadsValuesFromMigratedFileTest extends ilLanguageBaseTestCase
{
use CapturesErrorLog;

private ?string $fixture_directory = null;
/** True if this test defined CLIENT_DATA_DIR itself; tearDown() removes only such a root. */
private bool $created_client_data_dir_root = false;
Expand Down Expand Up @@ -538,10 +540,10 @@ public function testWithoutDirectoryManagerOnlyTheRowsOfModulesOfNoComponentAreS
['module' => 'rep_robj_xtst', 'identifier' => 'hello', 'value' => 'Welt'],
]);

$this->expectOutputRegex('/Without the language file directories the values of module "poll" are not shown/');
$values = ilObjLanguageExt::_getValues('de');

$this->assertSame(['rep_robj_xtst#:#hello' => 'Welt'], $values);
$this->assertLoggedErrorMatches('/Without the language file directories the values of module "poll" are not shown/');
}

/** A shipped `.po` that cannot be read: the module is missing (reported), its lng_data rows are not shown instead. */
Expand All @@ -558,8 +560,8 @@ public function testAnUnreadableShippedPoHidesTheModuleAndNeverShowsItsLngDataRo
['module' => 'rep_robj_xtst', 'identifier' => 'hello', 'value' => 'Welt'],
]);

$this->expectOutputRegex('/The language file of module "mtest" for "de" cannot be read, its values are not shown/');
$this->assertSame(['rep_robj_xtst#:#hello' => 'Welt'], ilObjLanguageExt::_getValues('de'));
$this->assertLoggedErrorMatches('/The language file of module "mtest" for "de" cannot be read, its values are not shown/');
}

public function testGetModulesIncludesAMigratedModuleEvenWithoutAnyLngDataRow(): void
Expand Down
80 changes: 80 additions & 0 deletions components/ILIAS/Language/tests/CapturesErrorLog.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*
*********************************************************************/

declare(strict_types=1);

use PHPUnit\Framework\Attributes\After;
use PHPUnit\Framework\Attributes\Before;

/** PHPUnit captures error_log() only after expectErrorLog(), otherwise it goes to stderr: log to a file instead. */
trait CapturesErrorLog
{
private ?string $captured_error_log_file = null;
private string $error_log_ini_before_test = '';
private bool $captured_error_log_was_read = false;

#[Before]
protected function redirectErrorLogToFile(): void
{
$file = tempnam(sys_get_temp_dir(), 'lngerrlog');
$this->assertNotFalse($file);
$this->captured_error_log_file = $file;
$this->captured_error_log_was_read = false;
$this->error_log_ini_before_test = (string) ini_get('error_log');
ini_set('error_log', $file);
}

/** Whatever was logged and not asserted on is a failure, as unexpected output was before. */
#[After]
protected function restoreErrorLog(): void
{
$file = $this->captured_error_log_file;
if ($file === null) {
return;
}
$this->captured_error_log_file = null;
try {
$unexpected = $this->captured_error_log_was_read || !is_file($file) ? '' : (string) file_get_contents($file);
} finally {
ini_set('error_log', $this->error_log_ini_before_test);
@unlink($file);
}
if ($unexpected !== '') {
$this->fail('Unexpected error_log(): ' . $unexpected);
}
}

protected function loggedErrors(): string
{
$this->captured_error_log_was_read = true;

return $this->captured_error_log_file !== null && is_file($this->captured_error_log_file)
? (string) file_get_contents($this->captured_error_log_file)
: '';
}

protected function assertLoggedErrorMatches(string $pattern): void
{
$this->assertMatchesRegularExpression($pattern, $this->loggedErrors());
}

protected function assertNothingLogged(): void
{
$this->assertSame('', $this->loggedErrors());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
*/
class MigratedLanguageFileSyncEditableTest extends MigratedLanguageFileSyncTestCase
{
use CapturesErrorLog;

/**
* @return array<string, string>|null identifier => value
*/
Expand Down Expand Up @@ -59,8 +61,8 @@ public function testAnUnreadableOverlayYieldsTheShippedPoAloneAndIsReported(): v
$this->seedOverlay(['greeting' => 'Moin']);
file_put_contents($this->overlayBase() . '.po', "msgid \"kaputt\n");

$this->expectOutputRegex('/The local changes of module "stest" for "de" cannot be read, the shipped values are used/');
$this->assertSame(['greeting' => 'Hallo'], $this->editable());
$this->assertLoggedErrorMatches('/The local changes of module "stest" for "de" cannot be read, the shipped values are used/');
}

/** For writing, the shipped values alone would drop the other local changes of the overlay: it throws instead. */
Expand All @@ -87,16 +89,19 @@ public function testWithoutClientDataDirectoryTheShippedPoAloneIsReturned(): voi
$this->seedShipped(['greeting' => 'Hallo']);

$this->assertSame(['greeting' => 'Hallo'], $this->editable(null));
$this->expectOutputString('');
$this->assertNothingLogged();
}

public function testAnUnreadableShippedPoThrows(): void
{
file_put_contents($this->shippedPo(), "msgid \"kaputt\n");

$this->expectOutputRegex('/cannot be read/');
$this->expectException(\RuntimeException::class);
$this->editable();
try {
$this->editable();
$this->fail('A RuntimeException was expected.');
} catch (\RuntimeException) {
$this->assertLoggedErrorMatches('/cannot be read/');
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
/** ilObjLanguageExt::withFuzzyMarkers(): the "not translated yet" marker falls back to the default language. */
class FuzzyMarkerDefaultLanguageTest extends ilLanguageBaseTestCase
{
use CapturesErrorLog;

/** @var list<string> */
private array $queries = [];

Expand Down Expand Up @@ -62,11 +64,11 @@ private function markersWith(string $user_text, ?string $default_text): array
public function testAMarkerMissingInTheUserLanguageComesFromTheDefaultLanguage(): void
{
// "lng" is shipped: the default language comes from the build (none here), never from lng_data
$this->expectOutputRegex('/There is no build of the language files/');
$comments = $this->markersWith('-lng_not_translated-', 'Not translated en');

$this->assertSame(['lng.#:#.a' => 'note - -lng_not_translated-', 'lng.#:#.b' => '-lng_not_translated-'], $comments);
$this->assertSame([], $this->queries);
$this->assertLoggedErrorMatches('/There is no build of the language files/');
}

public function testAMarkerOfTheUserLanguageIsUsedWithoutFurtherQuery(): void
Expand All @@ -79,10 +81,10 @@ public function testAMarkerOfTheUserLanguageIsUsedWithoutFurtherQuery(): void

public function testAMarkerMissingEverywhereStaysTheMissingKeyMarker(): void
{
$this->expectOutputRegex('/There is no build of the language files/');
$comments = $this->markersWith('-lng_not_translated-', null);

$this->assertSame('-lng_not_translated-', $comments['lng.#:#.b']);
$this->assertSame('note - -lng_not_translated-', $comments['lng.#:#.a']);
$this->assertLoggedErrorMatches('/There is no build of the language files/');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ public function module(): string
#[PreserveGlobalState(false)]
class PoMigrationLoadLanguageModuleTest extends ilLanguageBaseTestCase
{
use CapturesErrorLog;

protected function setUp(): void
{
parent::setUp();
Expand Down Expand Up @@ -288,8 +290,8 @@ public function testAShippedModuleWithoutABuildHasNoIdentifiersAndIsReported():
);

// a shipped module (lang/poll.pot) without a build: no identifiers, never the database
$this->expectOutputRegex('/There is no build of the language files/');
$this->assertSame([], MigratedPoFixture::servedTexts('poll', 'de', false));
$this->assertLoggedErrorMatches('/There is no build of the language files/');
}

/** "Migrated for a language" means a shipped `.po` exists: its absence yields no identifiers, not a missing overlay `.mo`. */
Expand All @@ -305,8 +307,8 @@ public function testNoIdentifiersAndNoReportWhenNoShippedPoExistsForThisLanguage
public function testAShippedModuleIsRecognisedByItsTemplateWithoutADirectoryManager(): void
{
// tos is recognised by its shipped template without the component graph; there is no build
$this->expectOutputRegex('/There is no build of the language files/');
$this->assertSame([], $this->callLoadFromMigratedLanguageFile('tos', 'de'));
$this->assertLoggedErrorMatches('/There is no build of the language files/');
}

/**
Expand Down Expand Up @@ -409,10 +411,10 @@ public function testACachedModuleOfAShippedModuleWithoutABuildIsNeitherServedNor
['tos' => ['tos_agreement' => 'FROM THE lng_modules CACHE']]
);

$this->expectOutputRegex('/There is no build of the language files/');
$language->loadLanguageModule('tos');

$this->assertSame('-tos_agreement-', $language->txt('tos_agreement'));
$this->assertLoggedErrorMatches('/There is no build of the language files/');
}

/** Setup/CLI: no build, no component graph - a LanguageIdentifier of a shipped module has no value and never reaches lng_data/lng_modules. */
Expand All @@ -421,8 +423,8 @@ public function testAnIdentifierOfAShippedModuleWithoutBuildAndComponentGraphIsN
$this->setGlobalVariable('ilDB', $this->databaseThatMustNotBeQueried());
$language = $this->buildLanguageWithoutRunningConstructor('de', 'en');

$this->expectOutputRegex('/There is no build of the language files/');
$this->assertSame('-tos_agreement-', $language->txt(PoMigrationLoadLanguageModuleTestTosIdentifier::AGREEMENT));
$this->assertLoggedErrorMatches('/There is no build of the language files/');
}

/** The prefix index is built per manager: another manager (e.g. a test or a re-initialised container) is never answered from the old one; the first directory of a prefix wins. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
#[PreserveGlobalState(false)]
class ShippedModulesNeverReadFromDatabaseTest extends ilLanguageBaseTestCase
{
use CapturesErrorLog;

private const string LANG_DIRECTORY = 'components/Vendor/Comp/lang/';

private string $root;
Expand Down Expand Up @@ -118,7 +120,6 @@ public function testTheOverlayOfIliasIniIsServedAfterTheSetupCreatedBuildAndClie
$this->buildShippedModule(false);
$this->forbidDatabase();

$this->expectOutputRegex('/There is no build of the language files/');
$this->assertSame('-greeting-', ilLanguage::_lookupEntry('de', 'zzs', 'greeting'), 'fresh installation: no client directory, no build');

$this->buildShippedModule();
Expand All @@ -130,6 +131,7 @@ public function testTheOverlayOfIliasIniIsServedAfterTheSetupCreatedBuildAndClie
MigratedTranslations::reset();

$this->assertSame('Overlay', ilLanguage::_lookupEntry('de', 'zzs', 'greeting'));
$this->assertLoggedErrorMatches('/There is no build of the language files/');
}

public function testTheClientDataDirectoryIsResolvedOncePerResetAlsoWhenMissing(): void
Expand Down Expand Up @@ -202,17 +204,17 @@ public function testWithoutABuildAShippedModuleHasNoValuesAndTheMissingBuildIsRe
$this->buildShippedModule(false);
$this->forbidDatabase();

$this->expectOutputRegex('/There is no build of the language files/');
$this->assertSame('-greeting-', ilLanguage::_lookupEntry('de', 'zzs', 'greeting'));
$this->assertLoggedErrorMatches('/There is no build of the language files/');
}

public function testALanguageWithAPoButNotInTheBuildIsReportedAsNotBuilt(): void
{
$this->buildShippedModule(true, true);
$this->forbidDatabase();

$this->expectOutputRegex('/ships a language file for "fr" that is not built/');
$this->assertSame('-greeting-', ilLanguage::_lookupEntry('fr', 'zzs', 'greeting'));
$this->assertLoggedErrorMatches('/ships a language file for "fr" that is not built/');
}

public function testALanguageWithoutAPoHasNoValuesAndNoReport(): void
Expand All @@ -221,7 +223,7 @@ public function testALanguageWithoutAPoHasNoValuesAndNoReport(): void
$this->forbidDatabase();

$this->assertSame('-greeting-', ilLanguage::_lookupEntry('it', 'zzs', 'greeting'));
$this->expectOutputString('');
$this->assertNothingLogged();
}

/**
Expand All @@ -239,7 +241,7 @@ public function testANotInstalledOrInvalidLanguageHasNoValuesNoReportAndNoDataba
$this->forbidDatabase(['en']);

$this->assertSame('-greeting-', ilLanguage::_lookupEntry($lang_key, 'zzs', 'greeting'));
$this->expectOutputString('');
$this->assertNothingLogged();
}

public function testAModuleOfNoComponentIsStillReadFromTheDatabase(): void
Expand Down
Loading