1 /* === This file is part of Calamares - <https://calamares.io> ===
2 *
3 * SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 *
6 *
7 * Calamares is Free Software: see the License-Identifier above.
8 *
9 *
10 */
11
12 #include "CalamaresUtilsSystem.h"
13 #include "Entropy.h"
14 #include "Logger.h"
15 #include "RAII.h"
16 #include "String.h"
17 #include "Traits.h"
18 #include "UMask.h"
19 #include "Variant.h"
20 #include "Yaml.h"
21
22 #include "GlobalStorage.h"
23 #include "JobQueue.h"
24
25 #include <QTemporaryFile>
26
27 #include <QtTest/QtTest>
28
29 #include <fcntl.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32
33 class LibCalamaresTests : public QObject
34 {
35 Q_OBJECT
36 public:
37 LibCalamaresTests();
38 ~LibCalamaresTests() override;
39
40 private Q_SLOTS:
41 void initTestCase();
42 void testDebugLevels();
43
44 void testLoadSaveYaml(); // Just settings.conf
45 void testLoadSaveYamlExtended(); // Do a find() in the src dir
46
47 void testCommands();
48
49 /** @section Test that all the UMask objects work correctly. */
50 void testUmask();
51
52 /** @section Tests the entropy functions. */
53 void testEntropy();
54 void testPrintableEntropy();
55 void testOddSizedPrintable();
56
57 /** @section Tests the RAII bits. */
58 void testPointerSetter();
59
60 /** @section Tests the Traits bits. */
61 void testTraits();
62
63 /** @section Testing the variants-methods */
64 void testVariantStringListCode();
65 void testVariantStringListYAMLDashed();
66 void testVariantStringListYAMLBracketed();
67
68 /** @section Test smart string truncation. */
69 void testStringTruncation();
70 void testStringTruncationShorter();
71 void testStringTruncationDegenerate();
72
73 private:
74 void recursiveCompareMap( const QVariantMap& a, const QVariantMap& b, int depth );
75 };
76
LibCalamaresTests()77 LibCalamaresTests::LibCalamaresTests() {}
78
~LibCalamaresTests()79 LibCalamaresTests::~LibCalamaresTests() {}
80
81 void
initTestCase()82 LibCalamaresTests::initTestCase()
83 {
84 }
85
86 void
testDebugLevels()87 LibCalamaresTests::testDebugLevels()
88 {
89 Logger::setupLogLevel( Logger::LOG_DISABLE );
90
91 QCOMPARE( Logger::logLevel(), static_cast< unsigned int >( Logger::LOG_DISABLE ) );
92
93 for ( unsigned int level = 0; level <= Logger::LOGVERBOSE; ++level )
94 {
95 Logger::setupLogLevel( level );
96 QCOMPARE( Logger::logLevel(), level );
97 QVERIFY( Logger::logLevelEnabled( level ) );
98
99 for ( unsigned int xlevel = 0; xlevel <= Logger::LOGVERBOSE; ++xlevel )
100 {
101 QCOMPARE( Logger::logLevelEnabled( xlevel ), xlevel <= level );
102 }
103 }
104 }
105
106 void
testLoadSaveYaml()107 LibCalamaresTests::testLoadSaveYaml()
108 {
109 Logger::setupLogLevel( Logger::LOGDEBUG );
110
111 QFile f( "settings.conf" );
112 // Find the nearest settings.conf to read
113 for ( unsigned int up = 0; !f.exists() && ( up < 4 ); ++up )
114 {
115 f.setFileName( QString( "../" ) + f.fileName() );
116 }
117 cDebug() << QDir().absolutePath() << f.fileName() << f.exists();
118 QVERIFY( f.exists() );
119
120 auto map = CalamaresUtils::loadYaml( f.fileName() );
121 QVERIFY( map.contains( "sequence" ) );
122 QCOMPARE( map[ "sequence" ].type(), QVariant::List );
123
124 // The source-repo example `settings.conf` has a show and an exec phase
125 auto sequence = map[ "sequence" ].toList();
126 cDebug() << "Loaded example `settings.conf` sequence:";
127 for ( const auto& v : sequence )
128 {
129 cDebug() << Logger::SubEntry << v;
130 QCOMPARE( v.type(), QVariant::Map );
131 QVERIFY( v.toMap().contains( "show" ) || v.toMap().contains( "exec" ) );
132 }
133
134 CalamaresUtils::saveYaml( "out.yaml", map );
135
136 auto other_map = CalamaresUtils::loadYaml( "out.yaml" );
137 CalamaresUtils::saveYaml( "out2.yaml", other_map );
138 QCOMPARE( map, other_map );
139
140 QFile::remove( "out.yaml" );
141 QFile::remove( "out2.yaml" );
142 }
143
144 static QStringList
findConf(const QDir & d)145 findConf( const QDir& d )
146 {
147 QStringList mine;
148 if ( d.exists() )
149 {
150 QString path = d.absolutePath();
151 path.append( d.separator() );
152 for ( const auto& confname : d.entryList( { "*.conf" } ) )
153 mine.append( path + confname );
154 for ( const auto& subdirname : d.entryList( QDir::AllDirs | QDir::NoDotAndDotDot ) )
155 {
156 QDir subdir( d );
157 subdir.cd( subdirname );
158 mine.append( findConf( subdir ) );
159 }
160 }
161 return mine;
162 }
163
164 void
recursiveCompareMap(const QVariantMap & a,const QVariantMap & b,int depth)165 LibCalamaresTests::recursiveCompareMap( const QVariantMap& a, const QVariantMap& b, int depth )
166 {
167 cDebug() << "Comparing depth" << depth << a.count() << b.count();
168 QCOMPARE( a.keys(), b.keys() );
169 for ( const auto& k : a.keys() )
170 {
171 cDebug() << Logger::SubEntry << k;
172 const auto& av = a[ k ];
173 const auto& bv = b[ k ];
174
175 if ( av.typeName() != bv.typeName() )
176 {
177 cDebug() << Logger::SubEntry << "a type" << av.typeName() << av;
178 cDebug() << Logger::SubEntry << "b type" << bv.typeName() << bv;
179 }
180 QCOMPARE( av.typeName(), bv.typeName() );
181 if ( av.canConvert< QVariantMap >() )
182 {
183 recursiveCompareMap( av.toMap(), bv.toMap(), depth + 1 );
184 }
185 else
186 {
187 QCOMPARE( av, bv );
188 }
189 }
190 }
191
192
193 void
testLoadSaveYamlExtended()194 LibCalamaresTests::testLoadSaveYamlExtended()
195 {
196 Logger::setupLogLevel( Logger::LOGDEBUG );
197 bool loaded_ok;
198 for ( const auto& confname : findConf( QDir( "../src" ) ) )
199 {
200 loaded_ok = true;
201 cDebug() << "Testing" << confname;
202 auto map = CalamaresUtils::loadYaml( confname, &loaded_ok );
203 QVERIFY( loaded_ok );
204 QVERIFY( CalamaresUtils::saveYaml( "out.yaml", map ) );
205 auto othermap = CalamaresUtils::loadYaml( "out.yaml", &loaded_ok );
206 QVERIFY( loaded_ok );
207 QCOMPARE( map.keys(), othermap.keys() );
208 recursiveCompareMap( map, othermap, 0 );
209 QCOMPARE( map, othermap );
210 }
211 QFile::remove( "out.yaml" );
212 }
213
214 void
testCommands()215 LibCalamaresTests::testCommands()
216 {
217 using CalamaresUtils::System;
218 auto r = System::runCommand( System::RunLocation::RunInHost, { "/bin/ls", "/tmp" } );
219
220 QVERIFY( r.getExitCode() == 0 );
221
222 QTemporaryFile tf( "/tmp/calamares-test-XXXXXX" );
223 QVERIFY( tf.open() );
224 QVERIFY( !tf.fileName().isEmpty() );
225
226 QFileInfo tfn( tf.fileName() );
227 QVERIFY( !r.getOutput().contains( tfn.fileName() ) );
228
229 // Run ls again, now that the file exists
230 r = System::runCommand( System::RunLocation::RunInHost, { "/bin/ls", "/tmp" } );
231 QVERIFY( r.getOutput().contains( tfn.fileName() ) );
232
233 // .. and without a working directory set, assume builddir != /tmp
234 r = System::runCommand( System::RunLocation::RunInHost, { "/bin/ls" } );
235 QVERIFY( !r.getOutput().contains( tfn.fileName() ) );
236
237 r = System::runCommand( System::RunLocation::RunInHost, { "/bin/ls" }, "/tmp" );
238 QVERIFY( r.getOutput().contains( tfn.fileName() ) );
239 }
240
241 void
testUmask()242 LibCalamaresTests::testUmask()
243 {
244 struct stat mystat;
245
246 QTemporaryFile ft;
247 QVERIFY( ft.open() );
248
249 // m gets the previous value of the mask (depends on the environment the
250 // test is run in, might be 002, might be 077), ..
251 mode_t m = CalamaresUtils::setUMask( 022 );
252 QCOMPARE( CalamaresUtils::setUMask( m ), mode_t( 022 ) ); // But now most recently set was 022
253
254 for ( mode_t i = 0; i <= 0777 /* octal! */; ++i )
255 {
256 QByteArray name = ( ft.fileName() + QChar( '.' ) + QString::number( i, 8 ) ).toLatin1();
257 CalamaresUtils::UMask um( i );
258 int fd = creat( name, 0777 );
259 QVERIFY( fd >= 0 );
260 close( fd );
261 QFileInfo fi( name );
262 QVERIFY( fi.exists() );
263 QCOMPARE( stat( name, &mystat ), 0 );
264 QCOMPARE( mystat.st_mode & 0777, 0777 & ~i );
265 QCOMPARE( unlink( name ), 0 );
266 }
267 QCOMPARE( CalamaresUtils::setUMask( 022 ), m );
268 QCOMPARE( CalamaresUtils::setUMask( m ), mode_t( 022 ) );
269 }
270
271 void
testEntropy()272 LibCalamaresTests::testEntropy()
273 {
274 QByteArray data;
275
276 auto r0 = CalamaresUtils::getEntropy( 0, data );
277 QCOMPARE( CalamaresUtils::EntropySource::None, r0 );
278 QCOMPARE( data.size(), 0 );
279
280 auto r1 = CalamaresUtils::getEntropy( 16, data );
281 QVERIFY( r1 != CalamaresUtils::EntropySource::None );
282 QCOMPARE( data.size(), 16 );
283 // This can randomly fail (but not often)
284 QVERIFY( data.at( data.size() - 1 ) != char( 0xcb ) );
285
286 auto r2 = CalamaresUtils::getEntropy( 8, data );
287 QVERIFY( r2 != CalamaresUtils::EntropySource::None );
288 QCOMPARE( data.size(), 8 );
289 QCOMPARE( r1, r2 );
290 // This can randomly fail (but not often)
291 QVERIFY( data.at( data.size() - 1 ) != char( 0xcb ) );
292 }
293
294 void
testPrintableEntropy()295 LibCalamaresTests::testPrintableEntropy()
296 {
297 QString s;
298
299 auto r0 = CalamaresUtils::getPrintableEntropy( 0, s );
300 QCOMPARE( CalamaresUtils::EntropySource::None, r0 );
301 QCOMPARE( s.length(), 0 );
302
303 auto r1 = CalamaresUtils::getPrintableEntropy( 16, s );
304 QVERIFY( r1 != CalamaresUtils::EntropySource::None );
305 QCOMPARE( s.length(), 16 );
306 for ( QChar c : s )
307 {
308 QVERIFY( c.isPrint() );
309 QCOMPARE( c.row(), uchar( 0 ) );
310 QVERIFY( c.cell() > 32 ); // ASCII SPACE
311 QVERIFY( c.cell() < 127 );
312 }
313 }
314
315 void
testOddSizedPrintable()316 LibCalamaresTests::testOddSizedPrintable()
317 {
318 QString s;
319 for ( int l = 0; l <= 37; ++l )
320 {
321 auto r = CalamaresUtils::getPrintableEntropy( l, s );
322 if ( l == 0 )
323 {
324 QCOMPARE( r, CalamaresUtils::EntropySource::None );
325 }
326 else
327 {
328 QVERIFY( r != CalamaresUtils::EntropySource::None );
329 }
330 QCOMPARE( s.length(), l );
331
332 for ( QChar c : s )
333 {
334 QVERIFY( c.isPrint() );
335 QCOMPARE( c.row(), uchar( 0 ) );
336 QVERIFY( c.cell() > 32 ); // ASCII SPACE
337 QVERIFY( c.cell() < 127 );
338 }
339 }
340 }
341
342 void
testPointerSetter()343 LibCalamaresTests::testPointerSetter()
344 {
345 int special = 17;
346
347 QCOMPARE( special, 17 );
348 {
349 cScopedAssignment p( &special );
350 }
351 QCOMPARE( special, 17 );
352 {
353 cScopedAssignment p( &special );
354 p = 18;
355 }
356 QCOMPARE( special, 18 );
357 {
358 cScopedAssignment p( &special );
359 p = 20;
360 p = 3;
361 }
362 QCOMPARE( special, 3 );
363 {
364 cScopedAssignment< int > p( nullptr );
365 }
366 QCOMPARE( special, 3 );
367 {
368 // "don't do this" .. order of destructors is important
369 cScopedAssignment p( &special );
370 cScopedAssignment q( &special );
371 p = 17;
372 }
373 QCOMPARE( special, 17 );
374 {
375 // "don't do this" .. order of destructors is important
376 cScopedAssignment p( &special );
377 cScopedAssignment q( &special );
378 p = 34;
379 q = 2;
380 // q destroyed first, then p
381 }
382 QCOMPARE( special, 34 );
383 }
384
385
386 /* Demonstration of Traits support for has-a-method or not.
387 *
388 * We have two classes, c1 and c2; one has a method do_the_thing() and the
389 * other does not. A third class, Thinginator, has a method thingify(),
390 * which should call do_the_thing() of its argument if it exists.
391 */
392
393 struct c1
394 {
do_the_thingc1395 int do_the_thing() { return 2; }
396 };
397 struct c2
398 {
399 };
400
401 DECLARE_HAS_METHOD( do_the_thing )
402
403 struct Thinginator
404 {
405 public:
406 /// When class T has function do_the_thing()
407 template < class T >
thingifyThinginator408 int thingify( T& t, const std::true_type& )
409 {
410 return t.do_the_thing();
411 }
412
413 template < class T >
thingifyThinginator414 int thingify( T&, const std::false_type& )
415 {
416 return -1;
417 }
418
419 template < class T >
thingifyThinginator420 int thingify( T& t )
421 {
422 return thingify( t, has_do_the_thing< T > {} );
423 }
424 };
425
426
427 void
testTraits()428 LibCalamaresTests::testTraits()
429 {
430 has_do_the_thing< c1 > x {};
431 has_do_the_thing< c2 > y {};
432
433 QVERIFY( x );
434 QVERIFY( !y );
435
436 c1 c1 {};
437 c2 c2 {};
438
439 QCOMPARE( c1.do_the_thing(), 2 );
440
441 Thinginator t;
442 QCOMPARE( t.thingify( c1 ), 2 ); // Calls c1::do_the_thing()
443 QCOMPARE( t.thingify( c2 ), -1 );
444 }
445
446 void
testVariantStringListCode()447 LibCalamaresTests::testVariantStringListCode()
448 {
449 using namespace CalamaresUtils;
450 const QString key( "strings" );
451 {
452 // Things that are not stringlists
453 QVariantMap m;
454 QCOMPARE( getStringList( m, key ), QStringList {} );
455 m.insert( key, 17 );
456 QCOMPARE( getStringList( m, key ), QStringList {} );
457 m.insert( key, QVariant {} );
458 QCOMPARE( getStringList( m, key ), QStringList {} );
459 }
460
461 {
462 // Things that are **like** stringlists
463 QVariantMap m;
464 m.insert( key, QString( "astring" ) );
465 QCOMPARE( getStringList( m, key ).count(), 1 );
466 QCOMPARE( getStringList( m, key ),
467 QStringList { "astring" } ); // A single string **can** be considered a stringlist!
468 m.insert( key, QString( "more strings" ) );
469 QCOMPARE( getStringList( m, key ).count(), 1 );
470 QCOMPARE( getStringList( m, key ), QStringList { "more strings" } );
471 m.insert( key, QString() );
472 QCOMPARE( getStringList( m, key ).count(), 1 );
473 QCOMPARE( getStringList( m, key ), QStringList { QString() } );
474 }
475
476 {
477 // Things that are definitely stringlists
478 QVariantMap m;
479 m.insert( key, QStringList { "aap", "noot" } );
480 QCOMPARE( getStringList( m, key ).count(), 2 );
481 QVERIFY( getStringList( m, key ).contains( "aap" ) );
482 QVERIFY( !getStringList( m, key ).contains( "mies" ) );
483 }
484 }
485
486 void
testVariantStringListYAMLDashed()487 LibCalamaresTests::testVariantStringListYAMLDashed()
488 {
489 using namespace CalamaresUtils;
490 const QString key( "strings" );
491
492 // Looks like a stringlist to me
493 QTemporaryFile f;
494 QVERIFY( f.open() );
495 f.write( R"(---
496 strings:
497 - aap
498 - noot
499 - mies
500 )" );
501 f.close();
502 bool ok = false;
503 QVariantMap m = loadYaml( f.fileName(), &ok );
504
505 QVERIFY( ok );
506 QCOMPARE( m.count(), 1 );
507 QVERIFY( m.contains( key ) );
508
509 QVERIFY( getStringList( m, key ).contains( "aap" ) );
510 QVERIFY( getStringList( m, key ).contains( "mies" ) );
511 QVERIFY( !getStringList( m, key ).contains( "lam" ) );
512 }
513
514 void
testVariantStringListYAMLBracketed()515 LibCalamaresTests::testVariantStringListYAMLBracketed()
516 {
517 using namespace CalamaresUtils;
518 const QString key( "strings" );
519
520 // Looks like a stringlist to me
521 QTemporaryFile f;
522 QVERIFY( f.open() );
523 f.write( R"(---
524 strings: [ aap, noot, mies ]
525 )" );
526 f.close();
527 bool ok = false;
528 QVariantMap m = loadYaml( f.fileName(), &ok );
529
530 QVERIFY( ok );
531 QCOMPARE( m.count(), 1 );
532 QVERIFY( m.contains( key ) );
533
534 QVERIFY( getStringList( m, key ).contains( "aap" ) );
535 QVERIFY( getStringList( m, key ).contains( "mies" ) );
536 QVERIFY( !getStringList( m, key ).contains( "lam" ) );
537 }
538
539 void
testStringTruncation()540 LibCalamaresTests::testStringTruncation()
541 {
542 Logger::setupLogLevel( Logger::LOGDEBUG );
543
544 using namespace CalamaresUtils;
545
546 const QString longString( R"(---
547 --- src/libcalamares/utils/String.h
548 +++ src/libcalamares/utils/String.h
549 @@ -62,15 +62,22 @@ DLLEXPORT QString removeDiacritics( const QString& string );
550 */
551 DLLEXPORT QString obscure( const QString& string );
552
553 +/** @brief Parameter for counting lines at beginning and end of string
554 + *
555 + * This is used by truncateMultiLine() to indicate how many lines from
556 + * the beginning and how many from the end should be kept.
557 + */
558 struct LinesStartEnd
559 {
560 - int atStart;
561 - int atEnd;
562 + int atStart = 0;
563 + int atEnd = 0;
564 )" );
565
566 const int sufficientLength = 812;
567 // There's 18 lines in all
568 QCOMPARE( longString.count( '\n' ), 18 );
569 QVERIFY( longString.length() < sufficientLength );
570
571 // If we ask for more, we get everything back
572 QCOMPARE( longString, truncateMultiLine( longString, LinesStartEnd { 20, 0 }, CharCount { sufficientLength } ) );
573 QCOMPARE( longString, truncateMultiLine( longString, LinesStartEnd { 0, 20 }, CharCount { sufficientLength } ) );
574
575 // If we ask for no lines, only characters, we get that
576 {
577 auto s = truncateMultiLine( longString, LinesStartEnd { 0, 0 }, CharCount { 4 } );
578 QCOMPARE( s.length(), 4 );
579 QCOMPARE( s, QString( "---\n" ) );
580 }
581 {
582 auto s = truncateMultiLine( longString, LinesStartEnd { 0, 0 }, CharCount { sufficientLength } );
583 QCOMPARE( s, longString );
584 }
585
586 // Lines at the start
587 {
588 auto s = truncateMultiLine( longString, LinesStartEnd { 4, 0 }, CharCount { sufficientLength } );
589 QVERIFY( s.length() > 1 );
590 QVERIFY( longString.startsWith( s ) );
591 cDebug() << "Result-line" << Logger::Quote << s;
592 QCOMPARE( s.count( '\n' ), 4 );
593 }
594
595 // Lines at the end
596 {
597 auto s = truncateMultiLine( longString, LinesStartEnd { 0, 4 }, CharCount { sufficientLength } );
598 QVERIFY( s.length() > 1 );
599 QVERIFY( longString.endsWith( s ) );
600 cDebug() << "Result-line" << Logger::Quote << s;
601 QCOMPARE( s.count( '\n' ), 4 );
602 }
603
604 // Lines at both ends
605 {
606 auto s = truncateMultiLine( longString, LinesStartEnd { 2, 2 }, CharCount { sufficientLength } );
607 QVERIFY( s.length() > 1 );
608 cDebug() << "Result-line" << Logger::Quote << s;
609 QCOMPARE( s.count( '\n' ), 4 );
610
611 auto firsttwo = truncateMultiLine( s, LinesStartEnd { 2, 0 }, CharCount { sufficientLength } );
612 auto lasttwo = truncateMultiLine( s, LinesStartEnd { 0, 2 }, CharCount { sufficientLength } );
613 QCOMPARE( firsttwo + lasttwo, s );
614 QCOMPARE( firsttwo.count( '\n' ), 2 );
615 QVERIFY( longString.startsWith( firsttwo ) );
616 QVERIFY( longString.endsWith( lasttwo ) );
617 }
618 }
619
620 void
testStringTruncationShorter()621 LibCalamaresTests::testStringTruncationShorter()
622 {
623 Logger::setupLogLevel( Logger::LOGDEBUG );
624
625 using namespace CalamaresUtils;
626
627 const QString longString( R"(Some strange string artifacts appeared, leading to `{1?}` being
628 displayed in various user-facing messages. These have been removed
629 and the translations updated.)" );
630 const char NEWLINE = '\n';
631
632 const int insufficientLength = 42;
633 // There's 2 newlines in all, no trailing newline
634 QVERIFY( !longString.endsWith( NEWLINE ) );
635 QCOMPARE( longString.count( NEWLINE ), 2 );
636 QVERIFY( longString.length() > insufficientLength );
637 // Even the first line must be more than the insufficientLength
638 QVERIFY( longString.indexOf( NEWLINE ) > insufficientLength );
639
640 // Grab first line, untruncated
641 {
642 auto s = truncateMultiLine( longString, LinesStartEnd { 1, 0 } );
643 QVERIFY( s.length() > 1 );
644 QVERIFY( longString.startsWith( s ) );
645 QVERIFY( s.endsWith( NEWLINE ) );
646 QVERIFY( s.endsWith( "being\n" ) );
647 QVERIFY( s.startsWith( "Some " ) );
648 }
649
650 // Grab last line, untruncated
651 {
652 auto s = truncateMultiLine( longString, LinesStartEnd { 0, 1 } );
653 QVERIFY( s.length() > 1 );
654 QVERIFY( longString.endsWith( s ) );
655 QVERIFY( !s.endsWith( NEWLINE ) );
656 QVERIFY( s.endsWith( "updated." ) );
657 QCOMPARE( s.count( NEWLINE ), 0 ); // Because last line doesn't end with a newline
658 QVERIFY( s.startsWith( "and the " ) );
659 }
660
661 // Grab last two lines, untruncated
662 {
663 auto s = truncateMultiLine( longString, LinesStartEnd { 0, 2 } );
664 QVERIFY( s.length() > 1 );
665 QVERIFY( longString.endsWith( s ) );
666 QVERIFY( !s.endsWith( NEWLINE ) );
667 QVERIFY( s.endsWith( "updated." ) );
668 QCOMPARE( s.count( NEWLINE ), 1 ); // Because last line doesn't end with a newline
669 QVERIFY( s.startsWith( "displayed in " ) );
670 }
671
672 // First line, truncated
673 {
674 auto s = truncateMultiLine( longString, LinesStartEnd { 1, 0 }, CharCount { insufficientLength } );
675 cDebug() << "Result-line" << Logger::Quote << s;
676 QVERIFY( s.length() > 1 );
677 QVERIFY( s.endsWith( NEWLINE ) );
678 QVERIFY( s.startsWith( "Some " ) );
679 // Because the first line has a newline, the truncated version does too,
680 // but that makes it one longer than requested.
681 QCOMPARE( s.length(), insufficientLength + 1 );
682 QVERIFY( longString.startsWith( s.left( insufficientLength ) ) );
683 }
684
685 // Last line, truncated; this line is quite short
686 {
687 const int quiteShort = 8;
688 QVERIFY( longString.lastIndexOf( NEWLINE ) < longString.length() - quiteShort );
689
690 auto s = truncateMultiLine( longString, LinesStartEnd { 0, 1 }, CharCount { quiteShort } );
691 cDebug() << "Result-line" << Logger::Quote << s;
692 QVERIFY( s.length() > 1 );
693 QVERIFY( !s.endsWith( NEWLINE ) ); // Because the original doesn't either
694 QVERIFY( s.startsWith( "upda" ) );
695 QCOMPARE( s.length(), quiteShort ); // No extra newlines
696 QVERIFY( longString.endsWith( s ) );
697 }
698
699 // First and last, but both truncated
700 {
701 const int quiteShort = 16;
702 QVERIFY( longString.indexOf( NEWLINE ) > quiteShort );
703 QVERIFY( longString.lastIndexOf( NEWLINE ) < longString.length() - quiteShort );
704
705 auto s = truncateMultiLine( longString, LinesStartEnd { 1, 1 }, CharCount { quiteShort } );
706 cDebug() << "Result-line" << Logger::Quote << s;
707 QVERIFY( s.length() > 1 );
708 QVERIFY( !s.endsWith( NEWLINE ) ); // Because the original doesn't either
709 QVERIFY( s.startsWith( "Some " ) );
710 QVERIFY( s.endsWith( "updated." ) );
711 QCOMPARE( s.length(), quiteShort + 1 ); // Newline between front and back part
712 }
713 }
714
715 void
testStringTruncationDegenerate()716 LibCalamaresTests::testStringTruncationDegenerate()
717 {
718 Logger::setupLogLevel( Logger::LOGDEBUG );
719
720 using namespace CalamaresUtils;
721
722 // This is quite long, 1 line only, with no newlines
723 const QString longString( "The portscout new distfile checker has detected that one or more of your "
724 "ports appears to be out of date. Please take the opportunity to check "
725 "each of the ports listed below, and if possible and appropriate, "
726 "submit/commit an update. If any ports have already been updated, you can "
727 "safely ignore the entry." );
728
729 const char NEWLINE = '\n';
730 const int quiteShort = 16;
731 QVERIFY( longString.length() > quiteShort );
732 QVERIFY( !longString.contains( NEWLINE ) );
733 QVERIFY( longString.indexOf( NEWLINE ) < 0 );
734
735 {
736 auto s = truncateMultiLine( longString, LinesStartEnd { 1, 0 }, CharCount { quiteShort } );
737 cDebug() << "Result-line" << Logger::Quote << s;
738 QVERIFY( s.length() > 1 );
739 QCOMPARE( s.length(), quiteShort ); // No newline between front and back part
740 QVERIFY( s.startsWith( "The port" ) ); // 8, which is quiteShort / 2
741 QVERIFY( s.endsWith( "e entry." ) ); // also 8 chars
742
743 auto t = truncateMultiLine( longString, LinesStartEnd { 2, 2 }, CharCount { quiteShort } );
744 QCOMPARE( s, t );
745 }
746 }
747
748
749 QTEST_GUILESS_MAIN( LibCalamaresTests )
750
751 #include "utils/moc-warnings.h"
752
753 #include "Tests.moc"
754