calamares/src/libcalamares/locale/Tests.cpp
Adriaan de Groot 028d424c73 [libcalamares] Expand testing of TZ location lookup
- Cape Town is in South Africa, so one might expect it to get South
  Africa's timezone -- which is Africa/Johannesburg -- but Windhoek
  is closer, so it gets that.
- Port Elisabeth is similar: Maseru lies between it an Johannesburg,
  so it gets the wrong timezone, too.

These both illustrate how the limited resolution of the map, together
with the "closest location" lookup, can give poor results. For most
of South Africa, the "wrong" timezone is closer than the right one.
2020-08-09 00:21:31 +10:00

432 lines
13 KiB
C++

/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Calamares is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "locale/LabelModel.h"
#include "locale/TimeZone.h"
#include "locale/TranslatableConfiguration.h"
#include "CalamaresVersion.h"
#include "utils/Logger.h"
#include <QtTest/QtTest>
class LocaleTests : public QObject
{
Q_OBJECT
public:
LocaleTests();
~LocaleTests() override;
private Q_SLOTS:
void initTestCase();
void testLanguageModelCount();
void testTranslatableLanguages();
void testTranslatableConfig1();
void testTranslatableConfig2();
void testLanguageScripts();
void testEsperanto();
void testInterlingue();
// TimeZone testing
void testRegions();
void testSimpleZones();
void testComplexZones();
void testTZLookup();
void testTZIterator();
void testLocationLookup_data();
void testLocationLookup();
};
LocaleTests::LocaleTests() {}
LocaleTests::~LocaleTests() {}
void
LocaleTests::initTestCase()
{
Logger::setupLogLevel( Logger::LOGDEBUG );
// Otherwise plain get() is dubious in the TranslatableConfiguration tests
QLocale::setDefault( QLocale( QStringLiteral( "en_US" ) ) );
QVERIFY( ( QLocale().name() == "C" ) || ( QLocale().name() == "en_US" ) );
}
void
LocaleTests::testLanguageModelCount()
{
const auto* m = CalamaresUtils::Locale::availableTranslations();
QVERIFY( m );
QVERIFY( m->rowCount( QModelIndex() ) > 1 );
int dutch = m->find( QLocale( "nl_NL" ) );
QVERIFY( dutch > 0 );
QCOMPARE( m->find( "NL" ), dutch );
// must be capitals
QCOMPARE( m->find( "nl" ), -1 );
QCOMPARE( m->find( QLocale( "nl" ) ), dutch );
// Belgium speaks Dutch as well
QCOMPARE( m->find( "BE" ), dutch );
}
void
LocaleTests::testLanguageScripts()
{
const auto* m = CalamaresUtils::Locale::availableTranslations();
QVERIFY( m );
// Cursory test that all the locales found have a sensible language,
// and that some specific languages have sensible corresponding data.
//
// This fails on Esperanto (or, if Esperanto is added to Qt, then
// this will pass and the test after the loop will fail.
for ( int i = 0; i < m->rowCount( QModelIndex() ); ++i )
{
const auto& label = m->locale( i );
const auto locale = label.locale();
cDebug() << label.label() << locale;
QVERIFY( locale.language() == QLocale::Greek ? locale.script() == QLocale::GreekScript : true );
QVERIFY( locale.language() == QLocale::Korean ? locale.script() == QLocale::KoreanScript : true );
QVERIFY( locale.language() == QLocale::Lithuanian ? locale.country() == QLocale::Lithuania : true );
QVERIFY( locale.language() != QLocale::C );
}
}
void
LocaleTests::testEsperanto()
{
#if QT_VERSION < QT_VERSION_CHECK( 5, 12, 2 )
QCOMPARE( QLocale( "eo" ).language(), QLocale::C );
#else
QCOMPARE( QLocale( "eo" ).language(), QLocale::Esperanto );
#endif
QCOMPARE( QLocale( QLocale::Esperanto ).language(), QLocale::Esperanto ); // Probably fails on 5.12, too
}
void
LocaleTests::testInterlingue()
{
// ie / Interlingue is borked (is "ie" even the right name?)
QCOMPARE( QLocale( "ie" ).language(), QLocale::C );
QCOMPARE( QLocale( QLocale::Interlingue ).language(), QLocale::English );
// "ia" exists (post-war variant of Interlingue)
QCOMPARE( QLocale( "ia" ).language(), QLocale::Interlingua );
// "bork" does not exist
QCOMPARE( QLocale( "bork" ).language(), QLocale::C );
}
static const QStringList&
someLanguages()
{
static QStringList languages { "nl", "de", "da", "nb", "sr@latin", "ar", "ru" };
return languages;
}
/** @brief Check consistency of test data
* Check that all the languages used in testing, are actually enabled
* in Calamares translations.
*/
void
LocaleTests::testTranslatableLanguages()
{
QStringList availableLanguages = QString( CALAMARES_TRANSLATION_LANGUAGES ).split( ';' );
cDebug() << "Translation languages:" << availableLanguages;
for ( const auto& language : someLanguages() )
{
// Could be QVERIFY, but then we don't see what language code fails
QCOMPARE( availableLanguages.contains( language ) ? language : QString(), language );
}
}
/** @brief Test strings with no translations
*/
void
LocaleTests::testTranslatableConfig1()
{
CalamaresUtils::Locale::TranslatedString ts0;
QVERIFY( ts0.isEmpty() );
QCOMPARE( ts0.count(), 1 ); // the empty string
CalamaresUtils::Locale::TranslatedString ts1( "Hello" );
QCOMPARE( ts1.count(), 1 );
QVERIFY( !ts1.isEmpty() );
QCOMPARE( ts1.get(), QStringLiteral( "Hello" ) );
QCOMPARE( ts1.get( QLocale( "nl" ) ), QStringLiteral( "Hello" ) );
QVariantMap map;
map.insert( "description", "description (no language)" );
CalamaresUtils::Locale::TranslatedString ts2( map, "description" );
QCOMPARE( ts2.count(), 1 );
QVERIFY( !ts2.isEmpty() );
QCOMPARE( ts2.get(), QStringLiteral( "description (no language)" ) );
QCOMPARE( ts2.get( QLocale( "nl" ) ), QStringLiteral( "description (no language)" ) );
}
/** @bref Test strings with translations.
*/
void
LocaleTests::testTranslatableConfig2()
{
QVariantMap map;
for ( const auto& language : someLanguages() )
{
map.insert( QString( "description[%1]" ).arg( language ),
QString( "description (language %1)" ).arg( language ) );
if ( language != "nl" )
{
map.insert( QString( "name[%1]" ).arg( language ), QString( "name (language %1)" ).arg( language ) );
}
}
// If there's no untranslated string in the map, it is considered empty
CalamaresUtils::Locale::TranslatedString ts0( map, "description" );
QVERIFY( ts0.isEmpty() ); // Because no untranslated string
QCOMPARE( ts0.count(),
someLanguages().count() + 1 ); // But there are entries for the translations, plus an empty string
// expand the map with untranslated entries
map.insert( QString( "description" ), "description (no language)" );
map.insert( QString( "name" ), "name (no language)" );
CalamaresUtils::Locale::TranslatedString ts1( map, "description" );
// The +1 is because "" is always also inserted
QCOMPARE( ts1.count(), someLanguages().count() + 1 );
QVERIFY( !ts1.isEmpty() );
QCOMPARE( ts1.get(), QStringLiteral( "description (no language)" ) ); // it wasn't set
QCOMPARE( ts1.get( QLocale( "nl" ) ), QStringLiteral( "description (language nl)" ) );
for ( const auto& language : someLanguages() )
{
// Skip Serbian (latin) because QLocale() constructed with it
// doesn't retain the @latin part.
if ( language == "sr@latin" )
{
continue;
}
// Could be QVERIFY, but then we don't see what language code fails
QCOMPARE( ts1.get( language ) == QString( "description (language %1)" ).arg( language ) ? language : QString(),
language );
}
QCOMPARE( ts1.get( QLocale( QLocale::Language::Serbian, QLocale::Script::LatinScript, QLocale::Country::Serbia ) ),
QStringLiteral( "description (language sr@latin)" ) );
CalamaresUtils::Locale::TranslatedString ts2( map, "name" );
// We skipped dutch this time
QCOMPARE( ts2.count(), someLanguages().count() );
QVERIFY( !ts2.isEmpty() );
// This key doesn't exist
CalamaresUtils::Locale::TranslatedString ts3( map, "front" );
QVERIFY( ts3.isEmpty() );
QCOMPARE( ts3.count(), 1 ); // The empty string
}
void
LocaleTests::testRegions()
{
using namespace CalamaresUtils::Locale;
RegionsModel regions;
QVERIFY( regions.rowCount( QModelIndex() ) > 3 ); // Africa, America, Asia
QStringList names;
for ( int i = 0; i < regions.rowCount( QModelIndex() ); ++i )
{
QVariant name = regions.data( regions.index( i ), RegionsModel::NameRole );
QVERIFY( name.isValid() );
QVERIFY( !name.toString().isEmpty() );
names.append( name.toString() );
}
QVERIFY( names.contains( "America" ) );
QVERIFY( !names.contains( "UTC" ) );
}
static void
displayedNames( QAbstractItemModel& model, QStringList& names )
{
names.clear();
for ( int i = 0; i < model.rowCount( QModelIndex() ); ++i )
{
QVariant name = model.data( model.index( i, 0 ), Qt::DisplayRole );
QVERIFY( name.isValid() );
QVERIFY( !name.toString().isEmpty() );
names.append( name.toString() );
}
}
void
LocaleTests::testSimpleZones()
{
using namespace CalamaresUtils::Locale;
ZonesModel zones;
QVERIFY( zones.rowCount( QModelIndex() ) > 24 );
QStringList names;
displayedNames( zones, names );
QVERIFY( names.contains( "Amsterdam" ) );
if ( !names.contains( "New York" ) )
{
for ( const auto& s : names )
{
if ( s.startsWith( 'N' ) )
{
cDebug() << s;
}
}
}
QVERIFY( names.contains( "New York" ) );
QVERIFY( !names.contains( "America" ) );
QVERIFY( !names.contains( "New_York" ) );
}
void
LocaleTests::testComplexZones()
{
using namespace CalamaresUtils::Locale;
ZonesModel zones;
RegionalZonesModel europe( &zones );
QStringList names;
displayedNames( zones, names );
QVERIFY( names.contains( "New York" ) );
QVERIFY( names.contains( "Prague" ) );
QVERIFY( names.contains( "Abidjan" ) );
// No region set
displayedNames( europe, names );
QVERIFY( names.contains( "New York" ) );
QVERIFY( names.contains( "Prague" ) );
QVERIFY( names.contains( "Abidjan" ) );
// Now filter
europe.setRegion( "Europe" );
displayedNames( europe, names );
QVERIFY( !names.contains( "New York" ) );
QVERIFY( names.contains( "Prague" ) );
QVERIFY( !names.contains( "Abidjan" ) );
europe.setRegion( "America" );
displayedNames( europe, names );
QVERIFY( names.contains( "New York" ) );
QVERIFY( !names.contains( "Prague" ) );
QVERIFY( !names.contains( "Abidjan" ) );
europe.setRegion( "Africa" );
displayedNames( europe, names );
QVERIFY( !names.contains( "New York" ) );
QVERIFY( !names.contains( "Prague" ) );
QVERIFY( names.contains( "Abidjan" ) );
}
void
LocaleTests::testTZLookup()
{
using namespace CalamaresUtils::Locale;
ZonesModel zones;
QVERIFY( zones.find( "America", "New_York" ) );
QCOMPARE( zones.find( "America", "New_York" )->zone(), QStringLiteral( "New_York" ) );
QCOMPARE( zones.find( "America", "New_York" )->tr(), QStringLiteral( "New York" ) );
QVERIFY( !zones.find( "Europe", "New_York" ) );
QVERIFY( !zones.find( "America", "New York" ) );
}
void
LocaleTests::testTZIterator()
{
using namespace CalamaresUtils::Locale;
const ZonesModel zones;
QVERIFY( zones.find( "Europe", "Rome" ) );
int count = 0;
bool seenRome = false;
bool seenGnome = false;
for ( auto it = zones.begin(); it; ++it )
{
QVERIFY( *it );
QVERIFY( !( *it )->zone().isEmpty() );
seenRome |= ( *it )->zone() == QStringLiteral( "Rome" );
seenGnome |= ( *it )->zone() == QStringLiteral( "Gnome" );
count++;
}
QVERIFY( seenRome );
QVERIFY( !seenGnome );
QCOMPARE( count, zones.rowCount( QModelIndex() ) );
QCOMPARE( zones.data( zones.index( 0 ), ZonesModel::RegionRole ).toString(), QStringLiteral( "Africa" ) );
QCOMPARE( ( *zones.begin() )->zone(), QStringLiteral( "Abidjan" ) );
}
void
LocaleTests::testLocationLookup_data()
{
QTest::addColumn< double >( "latitude" );
QTest::addColumn< double >( "longitude" );
QTest::addColumn< QString >( "name" );
QTest::newRow( "London" ) << 50.0 << 0.0 << QString( "London" );
QTest::newRow( "Tarawa E" ) << 0.0 << 179.0 << QString( "Tarawa" );
QTest::newRow( "Tarawa W" ) << 0.0 << -179.0 << QString( "Tarawa" );
QTest::newRow( "Johannesburg" ) << -26.0 << 28.0 << QString( "Johannesburg" ); // South Africa
QTest::newRow( "Maseru" ) << -29.0 << 27.0 << QString( "Maseru" ); // Lesotho
QTest::newRow( "Windhoek" ) << -22.0 << 17.0 << QString( "Windhoek" ); // Namibia
QTest::newRow( "Port Elisabeth" ) << -33.0 << 25.0 << QString( "Johannesburg" ); // South Africa
QTest::newRow( "Cape Town" ) << -33.0 << 18.0 << QString( "Johannesburg" ); // South Africa
}
void
LocaleTests::testLocationLookup()
{
const CalamaresUtils::Locale::ZonesModel zones;
QFETCH( double, latitude );
QFETCH( double, longitude );
QFETCH( QString, name );
const auto* zone = zones.find( latitude, longitude );
QVERIFY( zone );
QCOMPARE( zone->zone(), name );
}
QTEST_GUILESS_MAIN( LocaleTests )
#include "utils/moc-warnings.h"
#include "Tests.moc"