OSDN Git Service

bd2c6f8794a1c4038a51d9b1edd46a0ed66f2cb0
[alterlinux/alterlinux-calamares.git] / src / modules / partition / tests / PartitionJobTests.cpp
1 /* === This file is part of Calamares - <https://github.com/calamares> ===
2  *
3  *   Copyright 2014, Aurélien Gâteau <agateau@kde.org>
4  *   Copyright 2017, 2019 Adriaan de Groot <groot@kde.org>
5  *
6  *   Calamares is free software: you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation, either version 3 of the License, or
9  *   (at your option) any later version.
10  *
11  *   Calamares is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include "PartitionJobTests.h"
21
22 #include "core/KPMHelpers.h"
23 #include "jobs/CreatePartitionJob.h"
24 #include "jobs/CreatePartitionTableJob.h"
25 #include "jobs/ResizePartitionJob.h"
26
27 #include "partition/KPMManager.h"
28 #include "partition/PartitionQuery.h"
29 #include "utils/Logger.h"
30 #include "utils/Units.h"
31
32 #include <backend/corebackend.h>
33 #include <fs/filesystemfactory.h>
34
35 #include <QEventLoop>
36 #include <QProcess>
37 #include <QtTest/QtTest>
38
39 QTEST_GUILESS_MAIN( PartitionJobTests )
40
41 using namespace Calamares;
42 using CalamaresUtils::operator""_MiB;
43 using CalamaresUtils::Partition::isPartitionFreeSpace;
44
45 class PartitionMounter
46 {
47 public:
48     PartitionMounter( const QString& devicePath )
49         : m_mountPointDir( "calamares-partitiontests-mountpoint" )
50     {
51         QStringList args = QStringList() << devicePath << m_mountPointDir.path();
52         int ret = QProcess::execute( "mount", args );
53         m_mounted = ret == 0;
54         QCOMPARE( ret, 0 );
55     }
56
57     ~PartitionMounter()
58     {
59         if ( !m_mounted )
60         {
61             return;
62         }
63         int ret = QProcess::execute( "umount", QStringList() << m_mountPointDir.path() );
64         QCOMPARE( ret, 0 );
65     }
66
67     QString mountPoint() const { return m_mounted ? m_mountPointDir.path() : QString(); }
68
69 private:
70     QString m_devicePath;
71     QTemporaryDir m_mountPointDir;
72     bool m_mounted;
73 };
74
75 /// @brief Generate random data of given @p size as a QByteArray
76 static QByteArray
77 generateTestData( qint64 size )
78 {
79     QByteArray ba;
80     ba.resize( static_cast< int >( size ) );
81     // Fill the array explicitly to keep Valgrind happy
82     for ( auto it = ba.data(); it < ba.data() + size; ++it )
83     {
84         *it = char( rand() & 0xff );
85     }
86     return ba;
87 }
88
89 static void
90 writeFile( const QString& path, const QByteArray data )
91 {
92     QFile file( path );
93     QVERIFY( file.open( QIODevice::WriteOnly ) );
94
95     const char* ptr = data.constData();
96     const char* end = data.constData() + data.size();
97     const qint64 chunkSize = 16384;
98
99     while ( ptr < end )
100     {
101         qint64 count = file.write( ptr, chunkSize );
102         if ( count < 0 )
103         {
104             QString msg = QString( "Writing file failed. Only %1 bytes written out of %2. Error: '%3'." )
105                               .arg( ptr - data.constData() )
106                               .arg( data.size() )
107                               .arg( file.errorString() );
108             QFAIL( qPrintable( msg ) );
109         }
110         ptr += count;
111     }
112 }
113
114 static Partition*
115 firstFreePartition( PartitionNode* parent )
116 {
117     for ( auto child : parent->children() )
118         if ( isPartitionFreeSpace( child ) )
119         {
120             return child;
121         }
122     return nullptr;
123 }
124
125 //- QueueRunner ---------------------------------------------------------------
126 QueueRunner::QueueRunner( JobQueue* queue )
127     : m_queue( queue )
128     , m_finished( false )  // Same initalizations as in ::run()
129     , m_success( true )
130 {
131     connect( m_queue, &JobQueue::finished, this, &QueueRunner::onFinished );
132     connect( m_queue, &JobQueue::failed, this, &QueueRunner::onFailed );
133 }
134
135 QueueRunner::~QueueRunner()
136 {
137     // Nothing to do. We don't own the queue, and disconnect happens automatically
138 }
139
140 bool
141 QueueRunner::run()
142 {
143     m_finished = false;
144     m_success = true;
145     m_queue->start();
146     QEventLoop loop;
147     while ( !m_finished )
148     {
149         loop.processEvents();
150     }
151     return m_success;
152 }
153
154 void
155 QueueRunner::onFinished()
156 {
157     m_finished = true;
158 }
159
160 void
161 QueueRunner::onFailed( const QString& message, const QString& details )
162 {
163     m_success = false;
164     QString msg = message + "\ndetails: " + details;
165     QFAIL( qPrintable( msg ) );
166 }
167
168 CalamaresUtils::Partition::KPMManager* kpmcore = nullptr;
169
170 //- PartitionJobTests ------------------------------------------------------------------
171 PartitionJobTests::PartitionJobTests()
172     : m_runner( &m_queue )
173 {
174 }
175
176 void
177 PartitionJobTests::initTestCase()
178 {
179     QString devicePath = qgetenv( "CALAMARES_TEST_DISK" );
180     if ( devicePath.isEmpty() )
181     {
182         // The 0 is to keep the macro parameters happy
183         QSKIP( "Skipping test, CALAMARES_TEST_DISK is not set. It should point to a disk which can be safely formatted",
184                0 );
185     }
186
187     kpmcore = new CalamaresUtils::Partition::KPMManager();
188     FileSystemFactory::init();
189
190     refreshDevice();
191 }
192
193 void
194 PartitionJobTests::cleanupTestCase()
195 {
196     delete kpmcore;
197 }
198
199 void
200 PartitionJobTests::refreshDevice()
201 {
202     QString devicePath = qgetenv( "CALAMARES_TEST_DISK" );
203     m_device.reset( kpmcore->backend()->scanDevice( devicePath ) );
204     QVERIFY( !m_device.isNull() );
205 }
206
207 void
208 PartitionJobTests::testPartitionTable()
209 {
210     queuePartitionTableCreation( PartitionTable::msdos );
211     QVERIFY( m_runner.run() );
212     QVERIFY( m_device->partitionTable() );
213     QVERIFY( firstFreePartition( m_device->partitionTable() ) );
214
215     queuePartitionTableCreation( PartitionTable::gpt );
216     QVERIFY( m_runner.run() );
217     QVERIFY( m_device->partitionTable() );
218     QVERIFY( firstFreePartition( m_device->partitionTable() ) );
219 }
220
221 void
222 PartitionJobTests::queuePartitionTableCreation( PartitionTable::TableType type )
223 {
224     auto job = new CreatePartitionTableJob( m_device.data(), type );
225     job->updatePreview();
226     m_queue.enqueue( job_ptr( job ) );
227 }
228
229 CreatePartitionJob*
230 PartitionJobTests::newCreatePartitionJob( Partition* freeSpacePartition,
231                                           PartitionRole role,
232                                           FileSystem::Type type,
233                                           qint64 size )
234 {
235     Q_ASSERT( freeSpacePartition );
236
237     qint64 firstSector = freeSpacePartition->firstSector();
238     qint64 lastSector;
239
240     if ( size > 0 )
241     {
242         lastSector = firstSector + size / m_device->logicalSize();
243     }
244     else
245     {
246         lastSector = freeSpacePartition->lastSector();
247     }
248     FileSystem* fs = FileSystemFactory::create( type, firstSector, lastSector, m_device->logicalSize() );
249
250     Partition* partition = new Partition( freeSpacePartition->parent(),
251                                           *m_device,
252                                           role,
253                                           fs,
254                                           firstSector,
255                                           lastSector,
256                                           QString() /* path */,
257                                           KPM_PARTITION_FLAG( None ) /* availableFlags */,
258                                           QString() /* mountPoint */,
259                                           false /* mounted */,
260                                           KPM_PARTITION_FLAG( None ) /* activeFlags */,
261                                           KPM_PARTITION_STATE( New ) );
262     return new CreatePartitionJob( m_device.data(), partition );
263 }
264
265 void
266 PartitionJobTests::testCreatePartition()
267 {
268     queuePartitionTableCreation( PartitionTable::gpt );
269     CreatePartitionJob* job;
270     Partition* freePartition;
271
272     freePartition = firstFreePartition( m_device->partitionTable() );
273     QVERIFY( freePartition );
274     job = newCreatePartitionJob( freePartition, PartitionRole( PartitionRole::Primary ), FileSystem::Ext4, 1_MiB );
275     Partition* partition1 = job->partition();
276     QVERIFY( partition1 );
277     job->updatePreview();
278     m_queue.enqueue( job_ptr( job ) );
279
280     freePartition = firstFreePartition( m_device->partitionTable() );
281     QVERIFY( freePartition );
282     job = newCreatePartitionJob( freePartition, PartitionRole( PartitionRole::Primary ), FileSystem::LinuxSwap, 1_MiB );
283     Partition* partition2 = job->partition();
284     QVERIFY( partition2 );
285     job->updatePreview();
286     m_queue.enqueue( job_ptr( job ) );
287
288     freePartition = firstFreePartition( m_device->partitionTable() );
289     QVERIFY( freePartition );
290     job = newCreatePartitionJob( freePartition, PartitionRole( PartitionRole::Primary ), FileSystem::Fat32, 1_MiB );
291     Partition* partition3 = job->partition();
292     QVERIFY( partition3 );
293     job->updatePreview();
294     m_queue.enqueue( job_ptr( job ) );
295
296     QVERIFY( m_runner.run() );
297
298     // Check partitionPath has been set. It is not known until the job has
299     // executed.
300     QString devicePath = m_device->deviceNode();
301     QCOMPARE( partition1->partitionPath(), devicePath + "1" );
302     QCOMPARE( partition2->partitionPath(), devicePath + "2" );
303     QCOMPARE( partition3->partitionPath(), devicePath + "3" );
304 }
305
306 void
307 PartitionJobTests::testCreatePartitionExtended()
308 {
309     queuePartitionTableCreation( PartitionTable::msdos );
310     CreatePartitionJob* job;
311     Partition* freePartition;
312
313     freePartition = firstFreePartition( m_device->partitionTable() );
314     QVERIFY( freePartition );
315     job = newCreatePartitionJob( freePartition, PartitionRole( PartitionRole::Primary ), FileSystem::Ext4, 10_MiB );
316     Partition* partition1 = job->partition();
317     QVERIFY( partition1 );
318     job->updatePreview();
319     m_queue.enqueue( job_ptr( job ) );
320
321     freePartition = firstFreePartition( m_device->partitionTable() );
322     QVERIFY( freePartition );
323     job = newCreatePartitionJob(
324         freePartition, PartitionRole( PartitionRole::Extended ), FileSystem::Extended, 10_MiB );
325     job->updatePreview();
326     m_queue.enqueue( job_ptr( job ) );
327     Partition* extendedPartition = job->partition();
328
329     freePartition = firstFreePartition( extendedPartition );
330     QVERIFY( freePartition );
331     job = newCreatePartitionJob( freePartition, PartitionRole( PartitionRole::Logical ), FileSystem::Ext4, 0 );
332     Partition* partition2 = job->partition();
333     QVERIFY( partition2 );
334     job->updatePreview();
335     m_queue.enqueue( job_ptr( job ) );
336
337     QVERIFY( m_runner.run() );
338
339     // Check partitionPath has been set. It is not known until the job has
340     // executed.
341     QString devicePath = m_device->deviceNode();
342     QCOMPARE( partition1->partitionPath(), devicePath + "1" );
343     QCOMPARE( extendedPartition->partitionPath(), devicePath + "2" );
344     QCOMPARE( partition2->partitionPath(), devicePath + "5" );
345 }
346
347 void
348 PartitionJobTests::testResizePartition_data()
349 {
350     QTest::addColumn< unsigned int >( "oldStartMiB" );
351     QTest::addColumn< unsigned int >( "oldSizeMiB" );
352     QTest::addColumn< unsigned int >( "newStartMiB" );
353     QTest::addColumn< unsigned int >( "newSizeMiB" );
354
355     QTest::newRow( "grow" ) << 10 << 50 << 10 << 70;
356     QTest::newRow( "shrink" ) << 10 << 70 << 10 << 50;
357     QTest::newRow( "moveLeft" ) << 10 << 50 << 8 << 50;
358     QTest::newRow( "moveRight" ) << 10 << 50 << 12 << 50;
359 }
360
361 void
362 PartitionJobTests::testResizePartition()
363 {
364     QFETCH( unsigned int, oldStartMiB );
365     QFETCH( unsigned int, oldSizeMiB );
366     QFETCH( unsigned int, newStartMiB );
367     QFETCH( unsigned int, newSizeMiB );
368
369     const qint64 sectorsPerMiB = 1_MiB / m_device->logicalSize();
370
371     qint64 oldFirst = sectorsPerMiB * oldStartMiB;
372     qint64 oldLast = oldFirst + sectorsPerMiB * oldSizeMiB - 1;
373     qint64 newFirst = sectorsPerMiB * newStartMiB;
374     qint64 newLast = newFirst + sectorsPerMiB * newSizeMiB - 1;
375
376     // Make the test data file smaller than the full size of the partition to
377     // accomodate for the file system overhead
378     const unsigned long long minSizeMiB = qMin( oldSizeMiB, newSizeMiB );
379     const QByteArray testData = generateTestData( CalamaresUtils::MiBtoBytes( minSizeMiB ) * 3 / 4 );
380     const QString testName = "test.data";
381
382     // Setup: create the test partition
383     {
384         queuePartitionTableCreation( PartitionTable::msdos );
385
386         Partition* freePartition = firstFreePartition( m_device->partitionTable() );
387         QVERIFY( freePartition );
388         Partition* partition = KPMHelpers::createNewPartition( freePartition->parent(),
389                                                                *m_device,
390                                                                PartitionRole( PartitionRole::Primary ),
391                                                                FileSystem::Ext4,
392                                                                oldFirst,
393                                                                oldLast,
394                                                                KPM_PARTITION_FLAG( None ) );
395         CreatePartitionJob* job = new CreatePartitionJob( m_device.data(), partition );
396         job->updatePreview();
397         m_queue.enqueue( job_ptr( job ) );
398
399         QVERIFY( m_runner.run() );
400     }
401
402     {
403         // Write a test file in the partition
404         refreshDevice();
405         QVERIFY( m_device->partitionTable() );
406         Partition* partition
407             = m_device->partitionTable()->findPartitionBySector( oldFirst, PartitionRole( PartitionRole::Primary ) );
408         QVERIFY( partition );
409         QCOMPARE( partition->firstSector(), oldFirst );
410         QCOMPARE( partition->lastSector(), oldLast );
411         {
412             PartitionMounter mounter( partition->partitionPath() );
413             QString mountPoint = mounter.mountPoint();
414             QVERIFY( !mountPoint.isEmpty() );
415             writeFile( mountPoint + '/' + testName, testData );
416         }
417
418         // Resize
419         ResizePartitionJob* job = new ResizePartitionJob( m_device.data(), partition, newFirst, newLast );
420         job->updatePreview();
421         m_queue.enqueue( job_ptr( job ) );
422         QVERIFY( m_runner.run() );
423
424         QCOMPARE( partition->firstSector(), newFirst );
425         QCOMPARE( partition->lastSector(), newLast );
426     }
427
428     // Test
429     {
430         refreshDevice();
431         QVERIFY( m_device->partitionTable() );
432         Partition* partition
433             = m_device->partitionTable()->findPartitionBySector( newFirst, PartitionRole( PartitionRole::Primary ) );
434         QVERIFY( partition );
435         QCOMPARE( partition->firstSector(), newFirst );
436         QCOMPARE( partition->lastSector(), newLast );
437         QCOMPARE( partition->fileSystem().firstSector(), newFirst );
438         QCOMPARE( partition->fileSystem().lastSector(), newLast );
439
440
441         PartitionMounter mounter( partition->partitionPath() );
442         QString mountPoint = mounter.mountPoint();
443         QVERIFY( !mountPoint.isEmpty() );
444         {
445             QFile file( mountPoint + '/' + testName );
446             QVERIFY( file.open( QIODevice::ReadOnly ) );
447             QByteArray outData = file.readAll();
448             QCOMPARE( outData.size(), testData.size() );
449             QCOMPARE( outData, testData );
450         }
451     }
452 }