1414#include " Poco/Util/PropertyFileConfiguration.h"
1515#include " Poco/AutoPtr.h"
1616#include " Poco/Exception.h"
17+ #include " Poco/TemporaryFile.h"
18+ #include " Poco/FileStream.h"
1719#include < sstream>
1820#include < algorithm>
21+ #include " Poco/File.h"
22+ #include " Poco/Path.h"
1923
2024
2125using Poco::Util::PropertyFileConfiguration;
@@ -24,6 +28,7 @@ using Poco::AutoPtr;
2428using Poco::NotFoundException;
2529
2630
31+
2732PropertyFileConfigurationTest::PropertyFileConfigurationTest (const std::string& name): AbstractConfigurationTest(name)
2833{
2934}
@@ -119,6 +124,175 @@ void PropertyFileConfigurationTest::testSave()
119124}
120125
121126
127+ void PropertyFileConfigurationTest::testInclude ()
128+ {
129+ // Write an included properties file
130+ Poco::TemporaryFile includedFile;
131+ {
132+ Poco::FileOutputStream ostr (includedFile.path ());
133+ ostr << " included.prop1 = includedValue1\n " ;
134+ ostr << " included.prop2 = includedValue2\n " ;
135+ }
136+
137+ // Write a main properties file that includes the other using an absolute path
138+ Poco::TemporaryFile mainFile;
139+ {
140+ Poco::FileOutputStream ostr (mainFile.path ());
141+ ostr << " main.prop = mainValue\n " ;
142+ ostr << " !include " << includedFile.path () << " \n " ;
143+ ostr << " main.prop2 = mainValue2\n " ;
144+ }
145+
146+ AutoPtr<PropertyFileConfiguration> pConf = new PropertyFileConfiguration (mainFile.path ());
147+
148+ assertTrue (pConf->getString (" main.prop" ) == " mainValue" );
149+ assertTrue (pConf->getString (" main.prop2" ) == " mainValue2" );
150+ assertTrue (pConf->getString (" included.prop1" ) == " includedValue1" );
151+ assertTrue (pConf->getString (" included.prop2" ) == " includedValue2" );
152+
153+ // Relative include path (same directory, include by filename only)
154+ Poco::TemporaryFile includedFileRel;
155+ {
156+ Poco::FileOutputStream ostr (includedFileRel.path ());
157+ ostr << " includedRel.prop1 = includedRelValue1\n " ;
158+ ostr << " includedRel.prop2 = includedRelValue2\n " ;
159+ }
160+
161+ Poco::TemporaryFile mainFileRel;
162+ {
163+ Poco::FileOutputStream ostr (mainFileRel.path ());
164+ ostr << " mainRel.prop = mainRelValue\n " ;
165+ // include by filename only; should be resolved relative to mainFileRel
166+ ostr << " !include " << Poco::Path (includedFileRel.path ()).getFileName () << " \n " ;
167+ ostr << " mainRel.prop2 = mainRelValue2\n " ;
168+ }
169+
170+ AutoPtr<PropertyFileConfiguration> pConfRel = new PropertyFileConfiguration (mainFileRel.path ());
171+
172+ assertTrue (pConfRel->getString (" mainRel.prop" ) == " mainRelValue" );
173+ assertTrue (pConfRel->getString (" mainRel.prop2" ) == " mainRelValue2" );
174+ assertTrue (pConfRel->getString (" includedRel.prop1" ) == " includedRelValue1" );
175+ assertTrue (pConfRel->getString (" includedRel.prop2" ) == " includedRelValue2" );
176+
177+ // Nested includes: main includes A, A includes B (relative paths)
178+ Poco::TemporaryFile fileB;
179+ {
180+ Poco::FileOutputStream ostr (fileB.path ());
181+ ostr << " nestedB.prop = nestedBValue\n " ;
182+ }
183+
184+ Poco::TemporaryFile fileA;
185+ {
186+ Poco::FileOutputStream ostr (fileA.path ());
187+ // A includes B by filename
188+ ostr << " !include " << Poco::Path (fileB.path ()).getFileName () << " \n " ;
189+ ostr << " nestedA.prop = nestedAValue\n " ;
190+ }
191+
192+ Poco::TemporaryFile mainFileNested;
193+ {
194+ Poco::FileOutputStream ostr (mainFileNested.path ());
195+ ostr << " mainNested.prop = mainNestedValue\n " ;
196+ // main includes A by filename; A then includes B
197+ ostr << " !include " << Poco::Path (fileA.path ()).getFileName () << " \n " ;
198+ }
199+
200+ AutoPtr<PropertyFileConfiguration> pConfNested = new PropertyFileConfiguration (mainFileNested.path ());
201+
202+ assertTrue (pConfNested->getString (" mainNested.prop" ) == " mainNestedValue" );
203+ assertTrue (pConfNested->getString (" nestedA.prop" ) == " nestedAValue" );
204+ assertTrue (pConfNested->getString (" nestedB.prop" ) == " nestedBValue" );
205+
206+ // Non-existent include should throw
207+ Poco::TemporaryFile mainFile2;
208+ {
209+ Poco::FileOutputStream ostr (mainFile2.path ());
210+ ostr << " prop = value\n " ;
211+
212+ // Construct a guaranteed-nonexistent include path in the same directory
213+ Poco::Path includePath (mainFile2.path ());
214+ includePath.setFileName (" nonexistent_include.properties" );
215+ Poco::File includeFile (includePath);
216+ int counter = 0 ;
217+ while (includeFile.exists ())
218+ {
219+ includePath.setFileName (" nonexistent_include_" + std::to_string (++counter) + " .properties" );
220+ includeFile = Poco::File (includePath);
221+ }
222+
223+ ostr << " !include " << includePath.toString () << " \n " ;
224+ }
225+ try
226+ {
227+ AutoPtr<PropertyFileConfiguration> pConf2 = new PropertyFileConfiguration (mainFile2.path ());
228+ fail (" must throw" );
229+ }
230+ catch (Poco::FileException&)
231+ {
232+ }
233+
234+ // !include with no path should throw SyntaxException
235+ Poco::TemporaryFile mainFile3;
236+ {
237+ Poco::FileOutputStream ostr (mainFile3.path ());
238+ ostr << " prop = value\n " ;
239+ ostr << " !include \n " ;
240+ }
241+ try
242+ {
243+ AutoPtr<PropertyFileConfiguration> pConf3 = new PropertyFileConfiguration (mainFile3.path ());
244+ fail (" must throw" );
245+ }
246+ catch (Poco::SyntaxException&)
247+ {
248+ }
249+
250+ // !include with tab separator should work
251+ Poco::TemporaryFile includedFileTab;
252+ {
253+ Poco::FileOutputStream ostr (includedFileTab.path ());
254+ ostr << " tab.prop = tabValue\n " ;
255+ }
256+
257+ Poco::TemporaryFile mainFileTab;
258+ {
259+ Poco::FileOutputStream ostr (mainFileTab.path ());
260+ ostr << " !include\t " << includedFileTab.path () << " \n " ;
261+ }
262+
263+ AutoPtr<PropertyFileConfiguration> pConfTab = new PropertyFileConfiguration (mainFileTab.path ());
264+ assertTrue (pConfTab->getString (" tab.prop" ) == " tabValue" );
265+
266+ // !includeSomething should be treated as a regular comment
267+ Poco::TemporaryFile mainFile4;
268+ {
269+ Poco::FileOutputStream ostr (mainFile4.path ());
270+ ostr << " !includeSomething\n " ;
271+ ostr << " prop = value\n " ;
272+ }
273+
274+ AutoPtr<PropertyFileConfiguration> pConf4 = new PropertyFileConfiguration (mainFile4.path ());
275+ assertTrue (pConf4->getString (" prop" ) == " value" );
276+ assertTrue (!pConf4->hasProperty (" includeSomething" ));
277+
278+ // Self-include should throw (cyclic include detection)
279+ Poco::TemporaryFile selfFile;
280+ {
281+ Poco::FileOutputStream ostr (selfFile.path ());
282+ ostr << " prop = value\n " ;
283+ ostr << " !include " << selfFile.path () << " \n " ;
284+ }
285+ try
286+ {
287+ AutoPtr<PropertyFileConfiguration> pConf5 = new PropertyFileConfiguration (selfFile.path ());
288+ fail (" must throw" );
289+ }
290+ catch (Poco::FileException&)
291+ {
292+ }
293+ }
294+
295+
122296AbstractConfiguration::Ptr PropertyFileConfigurationTest::allocConfiguration () const
123297{
124298 return new PropertyFileConfiguration;
@@ -142,6 +316,7 @@ CppUnit::Test* PropertyFileConfigurationTest::suite()
142316 AbstractConfigurationTest_addTests (pSuite, PropertyFileConfigurationTest);
143317 CppUnit_addTest (pSuite, PropertyFileConfigurationTest, testLoad);
144318 CppUnit_addTest (pSuite, PropertyFileConfigurationTest, testSave);
319+ CppUnit_addTest (pSuite, PropertyFileConfigurationTest, testInclude);
145320
146321 return pSuite;
147322}
0 commit comments