ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
LanguageInstallationManagerMigratedSourcesAndLocksTest Class Reference
+ Inheritance diagram for LanguageInstallationManagerMigratedSourcesAndLocksTest:
+ Collaboration diagram for LanguageInstallationManagerMigratedSourcesAndLocksTest:

Public Member Functions

 testValuesOfAMigratedModuleComeFromThePoEvenWithoutAnyLangLine ()
 The tos lines could be removed from lang/ilias_xx.lang: the database is filled from the shipped .po alone. More...
 
 testLangLinesOfAMigratedModuleInNonLocalFilesAreIgnored ()
 
 testAModuleIsOnlyMigratedForLanguagesWithAShippedPo ()
 Only languages that ship a .po are migrated: for another language the .lang lines are used. More...
 
 testInstallWithoutLocalChangesWritesNoOverlayOnlyTheTakenOverRecord ()
 An installation without local changes writes no overlay file: the shipped state is served as is. More...
 
 testAModuleWithAnExistingOverlayIsLockedForTheWholeRunNotJustItsOwnSync ()
 A module that already has an overlay when insertLanguage() decides what to lock is locked (MigratedLanguageFileSync::withOverlayLock()) for the whole run, not just its final sync(), so a concurrent writer cannot interleave with the read-modify-write in between. More...
 
 testLocksAreAcquiredInSortedOrderRegardlessOfTheContributedDirectoryOrder ()
 Deadlock protection: modules with an overlay are locked in one global order (module name, byte-wise), whatever order getDirectories() hands them out in. More...
 
 testAModuleWithoutAnOverlayThatGetsOneCreatedDuringTheRunIsReportedAndTheOverlaySurvives ()
 A module without an overlay at that point is never locked (no lock file is created). More...
 

Additional Inherited Members

- Protected Member Functions inherited from LanguageInstallationManagerMigratedModulesTestCase
 setUp ()
 
 tearDown ()
 
 db ()
 
 repository ()
 
 manager (?\Closure $client_data_dir_resolver=null, ?\Closure $language_cache_invalidator=null, bool $with_resolver=true, bool $with_logger=true, ?LoggerInterface $logger=null)
 
 expectManagerWarning ()
 
 assertPostConditions ()
 
 loggedWarnings ()
 
 shipPo (array $entries, string $lang_key='de')
 
 shipSecondModule (array $entries)
 A second migrated module "pilot2" next to "pilot". More...
 
 seedOverlay (array $entries)
 
 overlayBase ()
 
 overlayPo ()
 
 effective ()
 Shipped .po plus overlay delta, as served (MigratedLanguageFileSync::loadModuleTranslations()) - the overlay itself only holds the entries that differ from the shipped .po. More...
 
 writeLang (string $relative_file, array $lines)
 
 resolveToken (string $token)
 
 lngData ()
 
 applyLngDataDelete (string $query, array $rows)
 Applies one recorded "DELETE FROM lng_data" to $rows, in the two shapes LanguageInstallationManager issues: the bulk delete of unchanged rows of a migrated module ("... local_change IS NULL AND module IN (...)") and the per-module delete of dropped identifiers ("... AND module = ? AND identifier IN (...)"). More...
 
 droppedIdentifiers (string $module)
 Identifiers of $module dropped via the per-module "DELETE FROM lng_data ... AND identifier IN (...)", as opposed to keys merely absent because they were never touched. More...
 
 lngModules ()
 
 errorLog ()
 What error_log() wrote during this test so far - PHPUnit redirects error_log to a capture file per test (call expectErrorLog() in tests that expect logging, so it is not echoed). More...
 
 shipPluralPo (array $entries, string $header)
 
 writeLocalOverlay (array $flat_values)
 
 seedOverlayMarkedAsMerged (array $entries, array $merged)
 
- Protected Attributes inherited from LanguageInstallationManagerMigratedModulesTestCase
const string NOW = '2026-01-02 03:04:05'
 
const string MODULE = 'pilot'
 
string $root
 
LanguageFileDirectory $directory
 
array $further_directories = []
 
array $quoted = []
 
array $queries = []
 
array $collation_rows = null
 
array $pending_rows = []
 
array $duplicate_rows = []
 
array $local_change_rows = []
 
array $db_local_changes = []
 
array $db_entries = []
 
array $available_languages = []
 
array $logged_warnings = []
 
const string TWO_FORMS = 'nplurals=2; plural=(n != 1);'
 
const string THREE_FORMS = 'nplurals=3; plural=(n == 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2);'
 

Detailed Description

Member Function Documentation

◆ testAModuleIsOnlyMigratedForLanguagesWithAShippedPo()

LanguageInstallationManagerMigratedSourcesAndLocksTest::testAModuleIsOnlyMigratedForLanguagesWithAShippedPo ( )

Only languages that ship a .po are migrated: for another language the .lang lines are used.

Definition at line 73 of file LanguageInstallationManagerMigratedSourcesAndLocksTest.php.

73 : void
74 {
75 $this->shipPo(['greeting' => 'Hallo'], 'de');
76 $this->writeLang('components/pilot/lang/ilias_en.lang', ['greeting#:#Hello']);
77
78 $this->manager()->insertLanguageForInstallation('en');
79
80 $this->assertSame(['greeting' => 'Hello'], $this->lngModules()[self::MODULE]);
81 $this->assertDirectoryDoesNotExist($this->root . '/client-data/lang');
82 }
manager(?\Closure $client_data_dir_resolver=null, ?\Closure $language_cache_invalidator=null, bool $with_resolver=true, bool $with_logger=true, ?LoggerInterface $logger=null)

References LanguageInstallationManagerMigratedModulesTestCase\lngModules(), LanguageInstallationManagerMigratedModulesTestCase\manager(), LanguageInstallationManagerMigratedModulesTestCase\shipPo(), and LanguageInstallationManagerMigratedModulesTestCase\writeLang().

+ Here is the call graph for this function:

◆ testAModuleWithAnExistingOverlayIsLockedForTheWholeRunNotJustItsOwnSync()

LanguageInstallationManagerMigratedSourcesAndLocksTest::testAModuleWithAnExistingOverlayIsLockedForTheWholeRunNotJustItsOwnSync ( )

A module that already has an overlay when insertLanguage() decides what to lock is locked (MigratedLanguageFileSync::withOverlayLock()) for the whole run, not just its final sync(), so a concurrent writer cannot interleave with the read-modify-write in between.

Definition at line 106 of file LanguageInstallationManagerMigratedSourcesAndLocksTest.php.

106 : void
107 {
108 $this->shipPo(['greeting' => 'Hallo']);
109 $this->seedOverlay(['greeting' => ['value' => 'Servus', 'original' => 'Hallo', 'local_change' => '2020-01-01T00:00:00Z']]);
110 $lock_file = MigratedPoFixture::overlayLock($this->overlayBase());
111 $held_locks = new ReflectionProperty(MigratedLanguageFileSync::class, 'held_locks');
112
113 $observed = null;
114 $manager = $this->manager(null, function () use (&$observed, $held_locks, $lock_file): void {
115 $observed = array_key_exists($lock_file, $held_locks->getValue());
116 });
117
118 $manager->insertLanguageForInstallation('de');
119
120 $this->assertTrue($observed, 'the module\'s overlay lock must already be held at this point in the run');
121 // released again once the whole run is done
122 $this->assertSame([], $held_locks->getValue());
123 }
static overlayLock(string $base_path)

References LanguageInstallationManagerMigratedModulesTestCase\manager(), LanguageInstallationManagerMigratedModulesTestCase\overlayBase(), MigratedPoFixture\overlayLock(), LanguageInstallationManagerMigratedModulesTestCase\seedOverlay(), and LanguageInstallationManagerMigratedModulesTestCase\shipPo().

+ Here is the call graph for this function:

◆ testAModuleWithoutAnOverlayThatGetsOneCreatedDuringTheRunIsReportedAndTheOverlaySurvives()

LanguageInstallationManagerMigratedSourcesAndLocksTest::testAModuleWithoutAnOverlayThatGetsOneCreatedDuringTheRunIsReportedAndTheOverlaySurvives ( )

A module without an overlay at that point is never locked (no lock file is created).

Definition at line 176 of file LanguageInstallationManagerMigratedSourcesAndLocksTest.php.

176 : void
177 {
178 $this->expectManagerWarning();
179 $this->shipPo(['greeting' => 'Hallo']);
180 $concurrent_edit = MigratedPoFixture::catalog(self::MODULE, [
181 'greeting' => ['value' => 'Von einem parallelen Admin-Edit', 'original' => 'Hallo', 'local_change' => '2020-01-01T00:00:00Z'],
182 ]);
183
184 $manager = $this->manager(null, function () use ($concurrent_edit): void {
185 MigratedPoFixture::writePair($this->overlayBase(), $concurrent_edit);
186 });
187
188 $unwritten = $manager->insertLanguageForInstallation('de');
189
190 $this->assertSame([self::MODULE], $unwritten);
191 $this->assertSame(
192 'Von einem parallelen Admin-Edit',
193 // untouched, so still as the fixture wrote it: with the module as msgctxt
194 $this->overlayPo()->find(self::MODULE, 'greeting')->getTranslation(),
195 'the concurrently-written overlay must survive untouched'
196 );
197 }
static writePair(string $base_path, TranslationCatalog $catalog)
Writes "<base>.po" and, like MigratedLanguageFileSync::sync(), its compiled revision named by current...
static catalog(string $module, array $entries)

References MigratedPoFixture\catalog(), LanguageInstallationManagerMigratedModulesTestCase\expectManagerWarning(), LanguageInstallationManagerMigratedModulesTestCase\manager(), LanguageInstallationManagerMigratedModulesTestCase\overlayBase(), LanguageInstallationManagerMigratedModulesTestCase\overlayPo(), LanguageInstallationManagerMigratedModulesTestCase\shipPo(), and MigratedPoFixture\writePair().

+ Here is the call graph for this function:

◆ testInstallWithoutLocalChangesWritesNoOverlayOnlyTheTakenOverRecord()

LanguageInstallationManagerMigratedSourcesAndLocksTest::testInstallWithoutLocalChangesWritesNoOverlayOnlyTheTakenOverRecord ( )

An installation without local changes writes no overlay file: the shipped state is served as is.

Definition at line 87 of file LanguageInstallationManagerMigratedSourcesAndLocksTest.php.

87 : void
88 {
89 $this->shipPo(['greeting' => ['value' => 'Hello', 'fuzzy' => true]]);
90
91 $this->manager()->insertLanguageForInstallation('de');
92
93 // only the record that the lng_data local changes were taken over (there were none)
94 $this->assertSame(['.locks', 'taken-over.de.json'], array_values(array_diff(scandir($this->root . '/client-data/lang'), ['.', '..'])));
95 $this->assertSame(
96 ['greeting' => ['value' => 'Hello', 'local_change' => false, 'local_change_date' => null, 'original' => 'Hello']],
97 $this->effective()
98 );
99 }
effective()
Shipped .po plus overlay delta, as served (MigratedLanguageFileSync::loadModuleTranslations()) - the ...

References LanguageInstallationManagerMigratedModulesTestCase\effective(), LanguageInstallationManagerMigratedModulesTestCase\manager(), and LanguageInstallationManagerMigratedModulesTestCase\shipPo().

+ Here is the call graph for this function:

◆ testLangLinesOfAMigratedModuleInNonLocalFilesAreIgnored()

LanguageInstallationManagerMigratedSourcesAndLocksTest::testLangLinesOfAMigratedModuleInNonLocalFilesAreIgnored ( )

Definition at line 58 of file LanguageInstallationManagerMigratedSourcesAndLocksTest.php.

58 : void
59 {
60 $this->shipPo(['greeting' => 'Hallo']);
61 $this->writeLang('lang/ilias_de.lang', ['pilot#:#greeting#:#Alt aus lang/', 'pilot#:#stale#:#Veraltet']);
62 $this->writeLang('components/pilot/lang/ilias_de.lang', ['greeting#:#Alt aus Komponente']);
63
64 $this->manager()->insertLanguageForInstallation('de');
65
66 $this->assertSame(['greeting' => 'Hallo'], $this->lngModules()[self::MODULE]);
67 $this->assertArrayNotHasKey('pilot|stale', $this->lngData());
68 }

References LanguageInstallationManagerMigratedModulesTestCase\lngData(), LanguageInstallationManagerMigratedModulesTestCase\lngModules(), LanguageInstallationManagerMigratedModulesTestCase\manager(), LanguageInstallationManagerMigratedModulesTestCase\shipPo(), and LanguageInstallationManagerMigratedModulesTestCase\writeLang().

+ Here is the call graph for this function:

◆ testLocksAreAcquiredInSortedOrderRegardlessOfTheContributedDirectoryOrder()

LanguageInstallationManagerMigratedSourcesAndLocksTest::testLocksAreAcquiredInSortedOrderRegardlessOfTheContributedDirectoryOrder ( )

Deadlock protection: modules with an overlay are locked in one global order (module name, byte-wise), whatever order getDirectories() hands them out in.

Definition at line 129 of file LanguageInstallationManagerMigratedSourcesAndLocksTest.php.

129 : void
130 {
131 $first_module = MigratedPoFixture::directory('zmod', 'components/pilot/lang/');
132 $second_module = MigratedPoFixture::directory('amod', 'components/pilot/lang/');
134 $this->root . '/components/pilot/lang/zmod_de.po',
135 MigratedPoFixture::catalog('zmod', ['greeting' => 'Hallo'])
136 );
138 $this->root . '/components/pilot/lang/amod_de.po',
139 MigratedPoFixture::catalog('amod', ['greeting' => 'Hallo'])
140 );
142 $this->root . '/client-data/lang/zmod/de/zmod_de',
143 MigratedPoFixture::catalog('zmod', ['greeting' => ['value' => 'Servus', 'original' => 'Hallo']])
144 );
146 $this->root . '/client-data/lang/amod/de/amod_de',
147 MigratedPoFixture::catalog('amod', ['greeting' => ['value' => 'Servus', 'original' => 'Hallo']])
148 );
149 $held_locks = new ReflectionProperty(MigratedLanguageFileSync::class, 'held_locks');
150 $observed_order = null;
151
152 $manager = new LanguageInstallationManager(
153 $this->db(),
154 // "zmod" contributed before "amod", the opposite of sorted order
155 new LanguageFileDirectoryManager(new CustomizingLanguageFileDirectory(), $first_module, $second_module),
156 $this->root,
157 $this->repository(),
158 static fn(): \DateTimeImmutable => new \DateTimeImmutable(self::NOW, new \DateTimeZone('UTC')),
159 new CallbackTranslationCache(function () use (&$observed_order, $held_locks): void {
160 $observed_order = array_map(
161 static fn(string $lock_file): string => basename(dirname($lock_file, 2)) . '/' . basename($lock_file),
162 array_keys($held_locks->getValue())
163 );
164 }),
165 fn(): string => $this->root . '/client-data'
166 );
167
168 $manager->insertLanguageForInstallation('de');
169
170 $this->assertSame(['amod/lock', 'zmod/lock'], $observed_order);
171 }
Lets a test observe or influence the invalidation.
Write access to the language installation: installing, flushing and registering languages in the data...
insertLanguageForInstallation(string $lang_key)
Installs or refreshes a language from every directory the LanguageFileDirectoryManager knows (includi...
static directory(string $prefix, string $path, bool $local=false)
static writePo(string $file, TranslationCatalog $catalog)

References MigratedPoFixture\catalog(), LanguageInstallationManagerMigratedModulesTestCase\db(), MigratedPoFixture\directory(), ILIAS\Language\Setup\LanguageInstallationManager\insertLanguageForInstallation(), LanguageInstallationManagerMigratedModulesTestCase\repository(), MigratedPoFixture\writePair(), and MigratedPoFixture\writePo().

+ Here is the call graph for this function:

◆ testValuesOfAMigratedModuleComeFromThePoEvenWithoutAnyLangLine()

LanguageInstallationManagerMigratedSourcesAndLocksTest::testValuesOfAMigratedModuleComeFromThePoEvenWithoutAnyLangLine ( )

The tos lines could be removed from lang/ilias_xx.lang: the database is filled from the shipped .po alone.

Definition at line 40 of file LanguageInstallationManagerMigratedSourcesAndLocksTest.php.

40 : void
41 {
42 $this->shipPo(['greeting' => 'Hallo', 'farewell' => 'Tschüss']);
43 $this->writeLang('lang/ilias_de.lang', ['common#:#yes#:#Ja']);
44
45 $this->manager()->insertLanguageForInstallation('de');
46
47 $this->assertSame(
48 [
49 'common|yes' => ['value' => 'Ja', 'local_change' => null],
50 'pilot|greeting' => ['value' => 'Hallo', 'local_change' => null],
51 'pilot|farewell' => ['value' => 'Tschüss', 'local_change' => null],
52 ],
53 $this->lngData()
54 );
55 $this->assertSame(['greeting' => 'Hallo', 'farewell' => 'Tschüss'], $this->lngModules()[self::MODULE]);
56 }

References LanguageInstallationManagerMigratedModulesTestCase\lngData(), LanguageInstallationManagerMigratedModulesTestCase\lngModules(), LanguageInstallationManagerMigratedModulesTestCase\manager(), LanguageInstallationManagerMigratedModulesTestCase\shipPo(), and LanguageInstallationManagerMigratedModulesTestCase\writeLang().

+ Here is the call graph for this function:

The documentation for this class was generated from the following file: