1 /* === This file is part of Calamares - <https://github.com/calamares> ===
3 * Copyright 2014, Aurélien Gâteau <agateau@kde.org>
4 * Copyright 2017, 2019 Adriaan de Groot <groot@kde.org>
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.
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.
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/>.
20 #include "PartitionJobTests.h"
22 #include "core/KPMHelpers.h"
23 #include "jobs/CreatePartitionJob.h"
24 #include "jobs/CreatePartitionTableJob.h"
25 #include "jobs/ResizePartitionJob.h"
27 #include "partition/KPMManager.h"
28 #include "partition/PartitionQuery.h"
29 #include "utils/Logger.h"
30 #include "utils/Units.h"
32 #include <backend/corebackend.h>
33 #include <fs/filesystemfactory.h>
37 #include <QtTest/QtTest>
39 QTEST_GUILESS_MAIN( PartitionJobTests )
41 using namespace Calamares;
42 using CalamaresUtils::operator""_MiB;
43 using CalamaresUtils::Partition::isPartitionFreeSpace;
45 class PartitionMounter
48 PartitionMounter( const QString& devicePath )
49 : m_mountPointDir( "calamares-partitiontests-mountpoint" )
51 QStringList args = QStringList() << devicePath << m_mountPointDir.path();
52 int ret = QProcess::execute( "mount", args );
63 int ret = QProcess::execute( "umount", QStringList() << m_mountPointDir.path() );
67 QString mountPoint() const { return m_mounted ? m_mountPointDir.path() : QString(); }
71 QTemporaryDir m_mountPointDir;
75 /// @brief Generate random data of given @p size as a QByteArray
77 generateTestData( qint64 size )
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 )
84 *it = char( rand() & 0xff );
90 writeFile( const QString& path, const QByteArray data )
93 QVERIFY( file.open( QIODevice::WriteOnly ) );
95 const char* ptr = data.constData();
96 const char* end = data.constData() + data.size();
97 const qint64 chunkSize = 16384;
101 qint64 count = file.write( ptr, chunkSize );
104 QString msg = QString( "Writing file failed. Only %1 bytes written out of %2. Error: '%3'." )
105 .arg( ptr - data.constData() )
107 .arg( file.errorString() );
108 QFAIL( qPrintable( msg ) );
115 firstFreePartition( PartitionNode* parent )
117 for ( auto child : parent->children() )
118 if ( isPartitionFreeSpace( child ) )
125 //- QueueRunner ---------------------------------------------------------------
126 QueueRunner::QueueRunner( JobQueue* queue )
128 , m_finished( false ) // Same initalizations as in ::run()
131 connect( m_queue, &JobQueue::finished, this, &QueueRunner::onFinished );
132 connect( m_queue, &JobQueue::failed, this, &QueueRunner::onFailed );
135 QueueRunner::~QueueRunner()
137 // Nothing to do. We don't own the queue, and disconnect happens automatically
147 while ( !m_finished )
149 loop.processEvents();
155 QueueRunner::onFinished()
161 QueueRunner::onFailed( const QString& message, const QString& details )
164 QString msg = message + "\ndetails: " + details;
165 QFAIL( qPrintable( msg ) );
168 CalamaresUtils::Partition::KPMManager* kpmcore = nullptr;
170 //- PartitionJobTests ------------------------------------------------------------------
171 PartitionJobTests::PartitionJobTests()
172 : m_runner( &m_queue )
177 PartitionJobTests::initTestCase()
179 QString devicePath = qgetenv( "CALAMARES_TEST_DISK" );
180 if ( devicePath.isEmpty() )
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",
187 kpmcore = new CalamaresUtils::Partition::KPMManager();
188 FileSystemFactory::init();
194 PartitionJobTests::cleanupTestCase()
200 PartitionJobTests::refreshDevice()
202 QString devicePath = qgetenv( "CALAMARES_TEST_DISK" );
203 m_device.reset( kpmcore->backend()->scanDevice( devicePath ) );
204 QVERIFY( !m_device.isNull() );
208 PartitionJobTests::testPartitionTable()
210 queuePartitionTableCreation( PartitionTable::msdos );
211 QVERIFY( m_runner.run() );
212 QVERIFY( m_device->partitionTable() );
213 QVERIFY( firstFreePartition( m_device->partitionTable() ) );
215 queuePartitionTableCreation( PartitionTable::gpt );
216 QVERIFY( m_runner.run() );
217 QVERIFY( m_device->partitionTable() );
218 QVERIFY( firstFreePartition( m_device->partitionTable() ) );
222 PartitionJobTests::queuePartitionTableCreation( PartitionTable::TableType type )
224 auto job = new CreatePartitionTableJob( m_device.data(), type );
225 job->updatePreview();
226 m_queue.enqueue( job_ptr( job ) );
230 PartitionJobTests::newCreatePartitionJob( Partition* freeSpacePartition,
232 FileSystem::Type type,
235 Q_ASSERT( freeSpacePartition );
237 qint64 firstSector = freeSpacePartition->firstSector();
242 lastSector = firstSector + size / m_device->logicalSize();
246 lastSector = freeSpacePartition->lastSector();
248 FileSystem* fs = FileSystemFactory::create( type, firstSector, lastSector, m_device->logicalSize() );
250 Partition* partition = new Partition( freeSpacePartition->parent(),
256 QString() /* path */,
257 KPM_PARTITION_FLAG( None ) /* availableFlags */,
258 QString() /* mountPoint */,
260 KPM_PARTITION_FLAG( None ) /* activeFlags */,
261 KPM_PARTITION_STATE( New ) );
262 return new CreatePartitionJob( m_device.data(), partition );
266 PartitionJobTests::testCreatePartition()
268 queuePartitionTableCreation( PartitionTable::gpt );
269 CreatePartitionJob* job;
270 Partition* freePartition;
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 ) );
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 ) );
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 ) );
296 QVERIFY( m_runner.run() );
298 // Check partitionPath has been set. It is not known until the job has
300 QString devicePath = m_device->deviceNode();
301 QCOMPARE( partition1->partitionPath(), devicePath + "1" );
302 QCOMPARE( partition2->partitionPath(), devicePath + "2" );
303 QCOMPARE( partition3->partitionPath(), devicePath + "3" );
307 PartitionJobTests::testCreatePartitionExtended()
309 queuePartitionTableCreation( PartitionTable::msdos );
310 CreatePartitionJob* job;
311 Partition* freePartition;
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 ) );
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();
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 ) );
337 QVERIFY( m_runner.run() );
339 // Check partitionPath has been set. It is not known until the job has
341 QString devicePath = m_device->deviceNode();
342 QCOMPARE( partition1->partitionPath(), devicePath + "1" );
343 QCOMPARE( extendedPartition->partitionPath(), devicePath + "2" );
344 QCOMPARE( partition2->partitionPath(), devicePath + "5" );
348 PartitionJobTests::testResizePartition_data()
350 QTest::addColumn< unsigned int >( "oldStartMiB" );
351 QTest::addColumn< unsigned int >( "oldSizeMiB" );
352 QTest::addColumn< unsigned int >( "newStartMiB" );
353 QTest::addColumn< unsigned int >( "newSizeMiB" );
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;
362 PartitionJobTests::testResizePartition()
364 QFETCH( unsigned int, oldStartMiB );
365 QFETCH( unsigned int, oldSizeMiB );
366 QFETCH( unsigned int, newStartMiB );
367 QFETCH( unsigned int, newSizeMiB );
369 const qint64 sectorsPerMiB = 1_MiB / m_device->logicalSize();
371 qint64 oldFirst = sectorsPerMiB * oldStartMiB;
372 qint64 oldLast = oldFirst + sectorsPerMiB * oldSizeMiB - 1;
373 qint64 newFirst = sectorsPerMiB * newStartMiB;
374 qint64 newLast = newFirst + sectorsPerMiB * newSizeMiB - 1;
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";
382 // Setup: create the test partition
384 queuePartitionTableCreation( PartitionTable::msdos );
386 Partition* freePartition = firstFreePartition( m_device->partitionTable() );
387 QVERIFY( freePartition );
388 Partition* partition = KPMHelpers::createNewPartition( freePartition->parent(),
390 PartitionRole( PartitionRole::Primary ),
394 KPM_PARTITION_FLAG( None ) );
395 CreatePartitionJob* job = new CreatePartitionJob( m_device.data(), partition );
396 job->updatePreview();
397 m_queue.enqueue( job_ptr( job ) );
399 QVERIFY( m_runner.run() );
403 // Write a test file in the partition
405 QVERIFY( m_device->partitionTable() );
407 = m_device->partitionTable()->findPartitionBySector( oldFirst, PartitionRole( PartitionRole::Primary ) );
408 QVERIFY( partition );
409 QCOMPARE( partition->firstSector(), oldFirst );
410 QCOMPARE( partition->lastSector(), oldLast );
412 PartitionMounter mounter( partition->partitionPath() );
413 QString mountPoint = mounter.mountPoint();
414 QVERIFY( !mountPoint.isEmpty() );
415 writeFile( mountPoint + '/' + testName, testData );
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() );
424 QCOMPARE( partition->firstSector(), newFirst );
425 QCOMPARE( partition->lastSector(), newLast );
431 QVERIFY( m_device->partitionTable() );
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 );
441 PartitionMounter mounter( partition->partitionPath() );
442 QString mountPoint = mounter.mountPoint();
443 QVERIFY( !mountPoint.isEmpty() );
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 );