/*
 * test.cpp
 * --------
 * Purpose: Unit tests for OpenMPT.
 * Notes  : We need FAAAAAAAR more unit tests!
 * Authors: OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#include "stdafx.h"
#include "test.h"


#ifdef ENABLE_TESTS

#ifdef LIBOPENMPT_BUILD
#include "mpt/arch/arch.hpp"
#include "mpt/arch/x86_amd64.hpp"
#endif
#include "mpt/base/check_platform.hpp"
#include "mpt/base/detect.hpp"
#include "mpt/base/numbers.hpp"
#include "mpt/crc/crc.hpp"
#include "mpt/environment/environment.hpp"
#ifdef MODPLUG_TRACKER
#include "../mptrack/TrackerSettings.h"
#include "mpt/fs/common_directories.hpp"
#include "mpt/fs/fs.hpp"
#endif // MODPLUG_TRACKER
#include "mpt/io/base.hpp"
#include "mpt/io/io.hpp"
#include "mpt/io/io_stdstream.hpp"
#include "mpt/io_file/fstream.hpp"
#include "mpt/io_read/filecursor_stdstream.hpp"
#include "mpt/osinfo/class.hpp"
#include "mpt/osinfo/dos_version.hpp"
#include "mpt/osinfo/dos_memory.hpp"
#include "mpt/parse/parse.hpp"
#include "mpt/parse/split.hpp"
#include "mpt/random/seed.hpp"
#include "mpt/test/test.hpp"
#include "mpt/test/test_macros.hpp"
#include "mpt/uuid/uuid.hpp"

#include "../common/version.h"
#include "../common/misc_util.h"
#include "../common/mptStringBuffer.h"
#include "../common/serialization_utils.h"
#include "../common/FileReader.h"
#include "../soundlib/Sndfile.h"
#include "../soundlib/AudioReadTarget.h"
#include "../soundlib/MIDIEvents.h"
#include "../soundlib/mod_specifications.h"
#include "../soundlib/MIDIEvents.h"
#include "../soundlib/MIDIMacros.h"
#include "openmpt/soundbase/Copy.hpp"
#include "openmpt/soundbase/SampleConvert.hpp"
#include "openmpt/soundbase/SampleDecode.hpp"
#include "openmpt/soundbase/SampleEncode.hpp"
#include "openmpt/soundbase/SampleFormat.hpp"
#include "../soundlib/SampleCopy.h"
#include "../soundlib/SampleNormalize.h"
#include "../soundlib/MIDIMacroParser.h"
#include "../soundlib/ModSampleCopy.h"
#include "../soundlib/ITCompression.h"
#include "../soundlib/tuningcollection.h"
#include "../soundlib/tuning.h"
#include "openmpt/soundbase/Dither.hpp"
#ifdef MODPLUG_TRACKER
#include "../mptrack/Mptrack.h"
#include "../mptrack/Moddoc.h"
#include "../mptrack/ModDocTemplate.h"
#include "../mptrack/Settings.h"
#include "../mptrack/SettingsIni.h"
#include "../mptrack/HTTP.h"
#endif // MODPLUG_TRACKER
#include "mpt/io_file/fileref.hpp"
#include "mpt/io_file/inputfile.hpp"
#include "mpt/io_file/outputfile.hpp"
#include "mpt/io_file_read/inputfile_filecursor.hpp"
#include "../common/mptFileIO.h"
#ifdef MODPLUG_TRACKER
#include "mpt/crypto/hash.hpp"
#include "mpt/crypto/jwk.hpp"
#include "mpt/uuid_namespace/uuid_namespace.hpp"
#endif // MODPLUG_TRACKER
#ifdef LIBOPENMPT_BUILD
#include "../libopenmpt/libopenmpt_version.h"
#endif // LIBOPENMPT_BUILD
#include "../soundlib/plugins/PlugInterface.h"
#include <sstream>
#include <limits>
#ifdef LIBOPENMPT_BUILD
#include <iomanip>
#include <iostream>
#endif // LIBOPENMPT_BUILD
#include <istream>
#include <ostream>
#include <stdexcept>
#ifdef LIBOPENMPT_BUILD
#include <cfenv>
#include <cfloat>
#endif // LIBOPENMPT_BUILD
#if MPT_COMPILER_MSVC
#include <tchar.h>
#endif
#if MPT_OS_WINDOWS
#include <windows.h>
#endif
#if defined(MPT_WITH_ZLIB)
#include <zlib.h>
#elif defined(MPT_WITH_MINIZ)
#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES
#if MPT_COMPILER_MSVC
#pragma warning(push)
#pragma warning(disable : 4505) // unreferenced function with internal linkage has been removed
#elif MPT_COMPILER_GCC
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#elif MPT_COMPILER_CLANG
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
#endif
#include <miniz/miniz.h>
#if MPT_COMPILER_MSVC
//#pragma warning(pop)
#elif MPT_COMPILER_GCC
#pragma GCC diagnostic pop
#elif MPT_COMPILER_CLANG
#pragma clang diagnostic pop
#endif
#endif

#define MPT_TEST_HAS_FILESYSTEM 1

#include "TestTools.h"

#if defined(LIBOPENMPT_BUILD)
#include "../libopenmpt/libopenmpt.hpp"
#endif


// enable tests which may fail spuriously
//#define FLAKY_TESTS



OPENMPT_NAMESPACE_BEGIN



namespace Test {



MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestVersion();
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestTypes();
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestMisc1();
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestMisc2();
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestRandom();
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestCharsets();
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestPathNative();
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestPathForeign();
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestStringFormatting();
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestSettings();
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestStringIO();
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestMIDIEvents();
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestSampleConversion();
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestITCompression();
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestPCnoteSerialization();
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestLoadSaveFile();
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestEditing();
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestMIDIMacroParser();



static mpt::PathString *PathPrefix = nullptr;
static mpt::default_prng * s_PRNG = nullptr;



mpt::PathString GetPathPrefix()
{
	if((*PathPrefix).empty())
	{
		return P_("");
	}
	return *PathPrefix + P_("/");
}


void PrintHeader()
{
	#ifdef LIBOPENMPT_BUILD
		std::cout << "libopenmpt test suite starting." << std::endl;
		std::cout << "libopenmpt library version: " << std::hex << std::setfill('0') << std::setw(8) << openmpt::get_library_version() << std::dec << std::endl;
		std::cout << "libopenmpt core version: " << std::hex << std::setfill('0') << std::setw(8) << openmpt::get_core_version() << std::dec << std::endl;
		std::cout << "libopenmpt library_version: " << openmpt::string::get("library_version") << std::endl;
		std::cout << "libopenmpt library_features: " << openmpt::string::get("library_features") << std::endl;
		std::cout << "libopenmpt core_version: " << openmpt::string::get("core_version") << std::endl;
		std::cout << "libopenmpt source_url: " << openmpt::string::get("source_url") << std::endl;
		std::cout << "libopenmpt source_date: " << openmpt::string::get("source_date") << std::endl;
		std::cout << "libopenmpt source_revision: " << openmpt::string::get("source_revision") << std::endl;
		std::cout << "libopenmpt source_is_modified: " << openmpt::string::get("source_is_modified") << std::endl;
		std::cout << "libopenmpt source_has_mixed_revisions: " << openmpt::string::get("source_has_mixed_revisions") << std::endl;
		std::cout << "libopenmpt source_is_package: " << openmpt::string::get("source_is_package") << std::endl;
		std::cout << "libopenmpt build: " << openmpt::string::get("build") << std::endl;
		std::cout << "libopenmpt build_compiler: " << openmpt::string::get("build_compiler") << std::endl;
	#endif
}

void PrintFooter()
{
	#ifdef LIBOPENMPT_BUILD
		std::cout << "libopenmpt test suite finished." << std::endl;
	#endif
}

void DoTests()
{

	#ifdef LIBOPENMPT_BUILD

		std::cout << std::flush;
		std::clog << std::flush;
		std::cerr << std::flush;
	
		std::cout << "libopenmpt test run" << std::endl;

		std::cout << "Version: " << mpt::ToCharset(mpt::Charset::ASCII, Build::GetVersionString(Build::StringVersion | Build::StringRevision | Build::StringSourceInfo | Build::StringBuildFlags | Build::StringBuildFeatures)) << std::endl;
		std::cout << "Compiler: " << mpt::ToCharset(mpt::Charset::ASCII, Build::GetBuildCompilerString()) << std::endl;
		std::cout << "OS: " << mpt::osinfo::get_class_name(mpt::osinfo::get_class()) << " (" << mpt::osinfo::get_sysname() << ")" << std::endl;
		#ifndef MPT_LIBC_QUIRK_NO_FENV
			{
				auto format_rounding = [](int rounding_mode) {
					std::string result;
					switch(rounding_mode)
					{
					case FE_DOWNWARD:
						result = "FE_DOWNWARD";
						break;
					case FE_TONEAREST:
						result = "FE_TONEAREST";
						break;
					case FE_TOWARDZERO:
						result = "FE_TOWARDZERO";
						break;
					case FE_UPWARD:
						result = "FE_UPWARD";
						break;
					default:
						result = "UNKNOWN: " + mpt::afmt::hex(rounding_mode);
						break;
					}
					return result;
				};
				auto get_rounding_mode = []() {
					#ifdef MPT_COMPILER_QUIRK_WANT_PRAGMA_STDC_FENV_ACCESS
					#pragma STDC FENV_ACCESS ON
					#endif
					return std::fegetround();
				};
				std::cout << "Rounding mode: " << format_rounding(get_rounding_mode()) << std::endl;
			}
		#endif // !MPT_LIBC_QUIRK_NO_FENV
		#if MPT_ARCH_X86 || MPT_ARCH_AMD64
			{
				const mpt::arch::x86::floating_point::control_state fpstate =
					#if MPT_ARCH_X86
						mpt::arch::x86::floating_point::get_state(mpt::arch::get_cpu_info());
					#else
						mpt::arch::x86::floating_point::get_state();
					#endif
				if(fpstate.x87_level)
				{
					auto format_rounding = [](uint16 fcw) {
						std::string result;
						switch((fcw & mpt::arch::x86::floating_point::FCW_RC) >> mpt::countr_zero(mpt::arch::x86::floating_point::FCW_RC))
						{
						case mpt::to_underlying(mpt::arch::x86::floating_point::rounding::nearest):
							result = "ROUND_NEAREST";
							break;
						case mpt::to_underlying(mpt::arch::x86::floating_point::rounding::down):
							result = "ROUND_DOWN";
							break;
						case mpt::to_underlying(mpt::arch::x86::floating_point::rounding::up):
							result = "ROUND_UP";
							break;
						case mpt::to_underlying(mpt::arch::x86::floating_point::rounding::zero):
							result = "ROUND_ZERO";
							break;
						}
						return result;
					};
					auto format_precision = [](uint16 fcw) {
						std::string result;
						switch((fcw & mpt::arch::x86::floating_point::FCW_PC) >> mpt::countr_zero(mpt::arch::x86::floating_point::FCW_PC))
						{
						case mpt::to_underlying(mpt::arch::x86::floating_point::precision::single24):
							result = "PRECISION_SINGLE";
							break;
						case mpt::to_underlying(mpt::arch::x86::floating_point::precision::double53):
							result = "PRECISION_DOUBLE";
							break;
						case mpt::to_underlying(mpt::arch::x86::floating_point::precision::extended64):
							result = "PRECISION_EXTENDED";
							break;
						case mpt::to_underlying(mpt::arch::x86::floating_point::precision::reserved):
							result = "PRECISION_UNDEFINED";
							break;
						}
						return result;
					};
					std::cout << "FCW: " << std::hex << std::setfill('0') << std::setw(4) << fpstate.x87fcw << std::dec << std::endl;
					std::cout << " " << format_rounding(fpstate.x87fcw);
					std::cout << " " << format_precision(fpstate.x87fcw);
					if (fpstate.x87_level <= 2) {
						std::cout << " " << (fpstate.x87fcw & mpt::arch::x86::floating_point::FCW_X) ? std::string("INFINITY-AFFINE") : std::string("INFINITY-PROJECTIVE");
					}
					std::cout << std::endl;
				}
				if(fpstate.mxcsr_mask)
				{
					auto format_rounding = [](uint32 csr) {
						std::string result;
						switch((csr & mpt::arch::x86::floating_point::MXCSR_RC) >> mpt::countr_zero(mpt::arch::x86::floating_point::MXCSR_RC))
						{
						case mpt::to_underlying(mpt::arch::x86::floating_point::rounding::nearest):
							result = "ROUND_NEAREST";
							break;
						case mpt::to_underlying(mpt::arch::x86::floating_point::rounding::down):
							result = "ROUND_DOWN";
							break;
						case mpt::to_underlying(mpt::arch::x86::floating_point::rounding::up):
							result = "ROUND_UP";
							break;
						case mpt::to_underlying(mpt::arch::x86::floating_point::rounding::zero):
							result = "ROUND_ZERO";
							break;
						}
						return result;
					};
					std::cout << "MXCSR (mask): " << std::hex << std::setfill('0') << std::setw(8) << fpstate.mxcsr << std::dec << " (" << std::hex << std::setfill('0') << std::setw(8) << fpstate.mxcsr_mask << std::dec << ")" << std::endl;
					std::cout << " " << format_rounding(fpstate.mxcsr);
					if(fpstate.mxcsr & fpstate.mxcsr_mask & mpt::arch::x86::floating_point::MXCSR_FTZ)
					{
						std::cout << " " << "FTZ";
					}
					if(fpstate.mxcsr & fpstate.mxcsr_mask & mpt::arch::x86::floating_point::MXCSR_DAZ)
					{
						std::cout << " " << "DAZ";
					}
					std::cout << std::endl;
				}
				if(fpstate.x87_level || fpstate.mxcsr_mask)
				{
					std::cout << "FP Exceptions: ";
					if(fpstate.x87_level)
					{
						if(!(fpstate.x87fcw & mpt::arch::x86::floating_point::FCW_IM)) std::cout << " x87/#I";
						if(!(fpstate.x87fcw & mpt::arch::x86::floating_point::FCW_DM)) std::cout << " x87/#D";
						if(!(fpstate.x87fcw & mpt::arch::x86::floating_point::FCW_ZM)) std::cout << " x87/#Z";
						if(!(fpstate.x87fcw & mpt::arch::x86::floating_point::FCW_OM)) std::cout << " x87/#O";
						if(!(fpstate.x87fcw & mpt::arch::x86::floating_point::FCW_UM)) std::cout << " x87/#U";
						if(!(fpstate.x87fcw & mpt::arch::x86::floating_point::FCW_PM)) std::cout << " x87/#P";
					}
					if(fpstate.mxcsr_mask)
					{
						if(!(fpstate.mxcsr & fpstate.mxcsr_mask & mpt::arch::x86::floating_point::MXCSR_IM)) std::cout << " sse/#I";
						if(!(fpstate.mxcsr & fpstate.mxcsr_mask & mpt::arch::x86::floating_point::MXCSR_DM)) std::cout << " sse/#D";
						if(!(fpstate.mxcsr & fpstate.mxcsr_mask & mpt::arch::x86::floating_point::MXCSR_ZM)) std::cout << " sse/#Z";
						if(!(fpstate.mxcsr & fpstate.mxcsr_mask & mpt::arch::x86::floating_point::MXCSR_OM)) std::cout << " sse/#O";
						if(!(fpstate.mxcsr & fpstate.mxcsr_mask & mpt::arch::x86::floating_point::MXCSR_UM)) std::cout << " sse/#U";
						if(!(fpstate.mxcsr & fpstate.mxcsr_mask & mpt::arch::x86::floating_point::MXCSR_PM)) std::cout << " sse/#P";
					}
					std::cout << std::endl;
				}
			}
		#endif // MPT_ARCH_X86 || MPT_ARCH_AMD64
		#if MPT_OS_DJGPP
			mpt::osinfo::dos::Version ver = mpt::osinfo::dos::Version::Current();
			std::cout << "DOS: " << ver.GetOEM() << " " << static_cast<int>(ver.GetSystemEmulated().Major) << "." << static_cast<int>(ver.GetSystemEmulated().Minor) << " (true " << static_cast<int>(ver.GetSystem().Major) << "." << static_cast<int>(ver.GetSystem().Minor) << ")" << std::endl;
			std::cout << "DPMI: " << static_cast<int>(ver.GetDPMI().Major) << "." << static_cast<int>(ver.GetDPMI().Minor) << std::endl;
			std::cout << "DPMI Host: " << ver.GetDPMIVendor() << " " << static_cast<int>(ver.GetDPMIHost().Major) << "." << static_cast<int>(ver.GetDPMIHost().Minor) << std::endl;
			std::cout << "Host OS: " << ver.GetHostName() << " " << ver.GetHostVersion() << "." << ver.GetHostRevision() << "." << ver.GetHostPatch() << " (" << "multitasking=" << static_cast<int>(ver.IsHostMultitasking()) << ", " << "fixedtimer=" << static_cast<int>(ver.HasHostFixedTimer()) << ")" << std::endl;
			std::cout << "BIOS Date: " << ver.GetBIOSDate() << std::endl;
			const mpt::osinfo::dos::memory_info mem = mpt::osinfo::dos::get_memory_info();
			auto print_mem = [](std::optional<std::size_t> value) -> std::string {
				if (!value) {
					return "???";
				}
				if (value >= (10ull * 1024 * 1024 * 1024)) {
					return MPT_AFORMAT("{}{}")(*value / 1024 / 1024 / 1024, "GiB");
				} else if (value >= (10 * 1024 * 1024)) {
					return MPT_AFORMAT("{}{}")(*value / 1024 / 1024, "MiB");
				} else if (value >= (10 * 1024)) {
					return MPT_AFORMAT("{}{}")(*value / 1024, "kiB");
				} else {
					return MPT_AFORMAT("{}{}")(*value, "B");
				}
			};
			std::cout << "Memory (virtual): " << print_mem(mem.get_virtual_used()) << " used / " << print_mem(mem.get_virtual_total()) << " total (" << print_mem(mem.get_virtual_free()) << " free, " << print_mem(mem.get_virtual_external_fragmentation()) << " fragmented)" << std::endl;
			std::cout << "RAM: " << print_mem(mem.get_physical_used()) << " used / " << print_mem(mem.get_physical_total()) << " total (" << print_mem(mem.get_physical_free()) << " free, " << print_mem(mem.get_physical_used_locked()) << " locked)" << std::endl;
			std::cout << "Swap: " << print_mem(mem.get_swap_total()) << " total" << std::endl;
		#endif // MPT_OS_DJGPP

		std::cout << std::flush;
		
	#endif

	#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)

		// prefix for test suite
		std::wstring pathprefix = std::wstring();

		bool libopenmpt = false;
		#ifdef LIBOPENMPT_BUILD
			libopenmpt = true;
		#endif

#if !MPT_OS_WINDOWS_WINRT
		// set path prefix for test files (if provided)
		std::vector<WCHAR> buf(GetEnvironmentVariableW(L"srcdir", NULL, 0) + 1);
		if(GetEnvironmentVariableW(L"srcdir", buf.data(), static_cast<DWORD>(buf.size())) > 0)
		{
			pathprefix = buf.data();
		} else
#endif
		if(libopenmpt && IsDebuggerPresent())
		{
			pathprefix = L"../../";
		}

		PathPrefix = new mpt::PathString(mpt::PathString::FromWide(pathprefix));

	#elif MPT_OS_WINDOWS
	
		// prefix for test suite
		std::string pathprefix = std::string();

		bool libopenmpt = false;
		#ifdef LIBOPENMPT_BUILD
			libopenmpt = true;
		#endif

#if !MPT_OS_WINDOWS_WINRT
		// set path prefix for test files (if provided)
		std::vector<CHAR> buf(GetEnvironmentVariable("srcdir", NULL, 0) + 1);
		if(GetEnvironmentVariable("srcdir", buf.data(), static_cast<DWORD>(buf.size())) > 0)
		{
			pathprefix = buf.data();
		} else
#endif
		if(libopenmpt && IsDebuggerPresent())
		{
			pathprefix = "../../";
		}

		PathPrefix = new mpt::PathString(mpt::PathString::FromLocale(pathprefix));

	#else

		// prefix for test suite
		mpt::ustring pathprefix = mpt::ustring();

		// set path prefix for test files (if provided)
		mpt::ustring env_srcdir = mpt::getenv( U_("srcdir") ).value_or( U_("") );
		if ( !env_srcdir.empty() ) {
			pathprefix = env_srcdir;
		}

		PathPrefix = new mpt::PathString(mpt::PathString::FromUnicode(pathprefix));

	#endif

	#if MPT_OS_DJGPP
		void (*TestLIBC)(void) = []() {
			VERIFY_EQUAL(mpt::platform::libc().is_ok(), true);
		};
		DO_TEST(TestLIBC);
	#endif

	void (*do_mpt_test)(void) = []() {
		mpt_test_reporter reporter{};
		mpt::test::run_all(reporter);
	};
	DO_TEST(do_mpt_test);

	mpt::random_device rd;
	s_PRNG = new mpt::default_prng(mpt::make_prng<mpt::default_prng>(rd));

	DO_TEST(TestVersion);
	DO_TEST(TestTypes);
	DO_TEST(TestMisc1);
	DO_TEST(TestMisc2);
	DO_TEST(TestRandom);
	DO_TEST(TestCharsets);
	DO_TEST(TestPathNative);
	DO_TEST(TestPathForeign);
	DO_TEST(TestStringFormatting);
	DO_TEST(TestSettings);
	DO_TEST(TestStringIO);
	DO_TEST(TestMIDIEvents);
	DO_TEST(TestSampleConversion);
	DO_TEST(TestITCompression);
	DO_TEST(TestMIDIMacroParser);

	// slower tests, require opening a CModDoc
	DO_TEST(TestPCnoteSerialization);
	DO_TEST(TestLoadSaveFile);
	DO_TEST(TestEditing);

	delete s_PRNG;
	s_PRNG = nullptr;

	delete PathPrefix;
	PathPrefix = nullptr;
}


static mpt::PathString GetTempFilenameBase();


static void RemoveFile(const mpt::PathString &filename)
{
	#if MPT_OS_WINDOWS
		for(int retry=0; retry<10; retry++)
		{
			if(DeleteFile(filename.AsNative().c_str()) != FALSE)
			{
				break;
			}
			// wait for windows virus scanners
			Sleep(10);
		}
	#else
		remove(filename.AsNative().c_str());
	#endif
}


// Test if functions related to program version data work
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestVersion()
{
	//Verify that macros and functions work.
	{
		VERIFY_EQUAL( Version::Parse(Version::Current().ToUString()), Version::Current() );
		VERIFY_EQUAL( Version::Parse(Version::Current().ToUString()).ToUString(), Version::Current().ToUString() );
		VERIFY_EQUAL( Version(18285096).ToUString(), U_("1.17.02.28") );
		VERIFY_EQUAL( Version::Parse(U_("1.17.02.28")), Version(18285096) );
		VERIFY_EQUAL( Version::Parse(U_("1.fe.02.28")), Version(0x01fe0228) );
		VERIFY_EQUAL( Version::Parse(U_("01.fe.02.28")), Version(0x01fe0228) );
		VERIFY_EQUAL( Version::Parse(U_("1.22")), Version(0x01220000) );
		VERIFY_EQUAL( MPT_V("1.17.02.28"), Version(18285096) );
		VERIFY_EQUAL( MPT_V("1.fe.02.28"), Version(0x01fe0228) );
		VERIFY_EQUAL( MPT_V("01.fe.02.28"), Version(0x01fe0228) );
		VERIFY_EQUAL( MPT_V("1.22"), Version(0x01220000) );
		VERIFY_EQUAL( MPT_V("1.19.02.00").WithoutTestNumber(), MPT_V("1.19.02.00"));
		VERIFY_EQUAL( MPT_V("1.18.03.20").WithoutTestNumber(), MPT_V("1.18.03.00"));
		VERIFY_EQUAL( MPT_V("1.18.01.13").IsTestVersion(), true);
		VERIFY_EQUAL( MPT_V("1.19.01.00").IsTestVersion(), false);
		VERIFY_EQUAL( MPT_V("1.17.02.54").IsTestVersion(), false);
		VERIFY_EQUAL( MPT_V("1.18.00.00").IsTestVersion(), false);
		VERIFY_EQUAL( MPT_V("1.18.02.00").IsTestVersion(), false);
		VERIFY_EQUAL( MPT_V("1.18.02.01").IsTestVersion(), true);

		// Ensure that versions ending in .00.00 (which are ambiguous to truncated version numbers in certain file formats (e.g. S3M and IT) do not get qualified as test builds.
		VERIFY_EQUAL( MPT_V("1.23.00.00").IsTestVersion(), false);

		static_assert( MPT_V("1.17.2.28").GetRawVersion() == 18285096 );
		static_assert( MPT_V("1.17.02.48").GetRawVersion() == 18285128 );
		static_assert( MPT_V("01.17.02.52").GetRawVersion() == 18285138 );
		// Ensure that bit-shifting works (used in some mod loaders for example)
		static_assert( MPT_V("01.17.00.00").GetRawVersion() == 0x0117 << 16 );
		static_assert( MPT_V("01.17.03.00").GetRawVersion() >> 8 == 0x011703 );

		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05-r13099")).IsNewerThan(VersionWithRevision::Parse(U_("1.30.00.05-r13100"))), false);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05-r13099")).IsNewerThan(VersionWithRevision::Parse(U_("1.30.00.05-r13099"))), false);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05-r13099")).IsNewerThan(VersionWithRevision::Parse(U_("1.30.00.05-r13098"))), true);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05-r13099")).IsNewerThan(VersionWithRevision::Parse(U_("1.30.00.05"))), false);

		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05-r13099")).IsOlderThan(VersionWithRevision::Parse(U_("1.30.00.05-r13100"))), true);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05-r13099")).IsOlderThan(VersionWithRevision::Parse(U_("1.30.00.05-r13099"))), false);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05-r13099")).IsOlderThan(VersionWithRevision::Parse(U_("1.30.00.05-r13098"))), false);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05-r13099")).IsOlderThan(VersionWithRevision::Parse(U_("1.30.00.05"))), false);

		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05")).IsNewerThan(VersionWithRevision::Parse(U_("1.30.00.05-r13100"))), false);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05")).IsNewerThan(VersionWithRevision::Parse(U_("1.30.00.05-r13099"))), false);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05")).IsNewerThan(VersionWithRevision::Parse(U_("1.30.00.05-r13098"))), false);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05")).IsNewerThan(VersionWithRevision::Parse(U_("1.30.00.05"))), false);

		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05")).IsOlderThan(VersionWithRevision::Parse(U_("1.30.00.05-r13100"))), false);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05")).IsOlderThan(VersionWithRevision::Parse(U_("1.30.00.05-r13099"))), false);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05")).IsOlderThan(VersionWithRevision::Parse(U_("1.30.00.05-r13098"))), false);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05")).IsOlderThan(VersionWithRevision::Parse(U_("1.30.00.05"))), false);

		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05-r13099")).IsEqualTo(VersionWithRevision::Parse(U_("1.30.00.05-r13100"))), false);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05-r13099")).IsEqualTo(VersionWithRevision::Parse(U_("1.30.00.05-r13099"))), true);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05-r13099")).IsEqualTo(VersionWithRevision::Parse(U_("1.30.00.05-r13098"))), false);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05-r13099")).IsEqualTo(VersionWithRevision::Parse(U_("1.30.00.05"))), false);

		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05-r13099")).IsEquivalentTo(VersionWithRevision::Parse(U_("1.30.00.05-r13100"))), false);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05-r13099")).IsEquivalentTo(VersionWithRevision::Parse(U_("1.30.00.05-r13099"))), true);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05-r13099")).IsEquivalentTo(VersionWithRevision::Parse(U_("1.30.00.05-r13098"))), false);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05-r13099")).IsEquivalentTo(VersionWithRevision::Parse(U_("1.30.00.05"))), true);

		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05")).IsEqualTo(VersionWithRevision::Parse(U_("1.30.00.05-r13100"))), false);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05")).IsEqualTo(VersionWithRevision::Parse(U_("1.30.00.05-r13099"))), false);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05")).IsEqualTo(VersionWithRevision::Parse(U_("1.30.00.05-r13098"))), false);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05")).IsEqualTo(VersionWithRevision::Parse(U_("1.30.00.05"))), true);

		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05")).IsEquivalentTo(VersionWithRevision::Parse(U_("1.30.00.05-r13100"))), true);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05")).IsEquivalentTo(VersionWithRevision::Parse(U_("1.30.00.05-r13099"))), true);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05")).IsEquivalentTo(VersionWithRevision::Parse(U_("1.30.00.05-r13098"))), true);
		VERIFY_EQUAL(VersionWithRevision::Parse(U_("1.30.00.05")).IsEquivalentTo(VersionWithRevision::Parse(U_("1.30.00.05"))), true);

	}

#ifdef MODPLUG_TRACKER
	//Verify that the version obtained from the executable file is the same as
	//defined in Version.
	{
		WCHAR szFullPath[MAX_PATH];
		DWORD dwVerHnd;
		DWORD dwVerInfoSize;

		// Get version information from the application
		::GetModuleFileNameW(NULL, szFullPath, mpt::saturate_cast<DWORD>(std::size(szFullPath)));
		dwVerInfoSize = ::GetFileVersionInfoSizeW(szFullPath, &dwVerHnd);
		if (!dwVerInfoSize)
			throw std::runtime_error("!dwVerInfoSize is true");

		std::vector<TCHAR> pVersionInfo(dwVerInfoSize);

		WCHAR *szVer = nullptr;
		UINT uVerLength;
		if (!(::GetFileVersionInfoW(szFullPath, (DWORD)dwVerHnd,
								   (DWORD)dwVerInfoSize, pVersionInfo.data())))
		{
			throw std::runtime_error("GetFileVersionInfo() returned false");
		}
		if (!(::VerQueryValueW(pVersionInfo.data(), L"\\StringFileInfo\\040904b0\\FileVersion",
							  (LPVOID*)&szVer, &uVerLength))) {
			throw std::runtime_error("VerQueryValue() returned false");
		}

		mpt::ustring version = mpt::ToUnicode(szVer);

		VERIFY_EQUAL( version, mpt::ufmt::val(Version::Current()) );
		VERIFY_EQUAL( Version::Parse(version), Version::Current() );
	}
#endif

#ifdef LIBOPENMPT_BUILD
#if MPT_TEST_HAS_FILESYSTEM
#if !MPT_OS_DJGPP
	mpt::PathString version_mk = GetPathPrefix() + P_("libopenmpt/libopenmpt_version.mk");
	mpt::IO::ifstream f(version_mk, std::ios::in);
	VERIFY_EQUAL(f ? true : false, true);
	std::map<std::string, std::string> fields;
	std::string line;
	while(std::getline(f, line))
	{
		line = mpt::trim(line);
		if(line.empty())
		{
			continue;
		}
		std::vector<std::string> line_fields = mpt::split(line, std::string("="));
		VERIFY_EQUAL_NONCONT(line_fields.size(), 2u);
		line_fields[0] = mpt::trim(line_fields[0]);
		line_fields[1] = mpt::trim(line_fields[1]);
		VERIFY_EQUAL_NONCONT(line_fields[0].length() > 0, true);
		fields[line_fields[0]] = line_fields[1];
	}
	VERIFY_EQUAL(fields["LIBOPENMPT_VERSION_MAJOR"], mpt::afmt::val(OPENMPT_API_VERSION_MAJOR));
	VERIFY_EQUAL(fields["LIBOPENMPT_VERSION_MINOR"], mpt::afmt::val(OPENMPT_API_VERSION_MINOR));
	VERIFY_EQUAL(fields["LIBOPENMPT_VERSION_PATCH"], mpt::afmt::val(OPENMPT_API_VERSION_PATCH));
	VERIFY_EQUAL(fields["LIBOPENMPT_VERSION_PREREL"], mpt::afmt::val(OPENMPT_API_VERSION_PREREL));
	if(std::string(OPENMPT_API_VERSION_PREREL).length() > 0)
	{
		VERIFY_EQUAL(std::string(OPENMPT_API_VERSION_PREREL).substr(0, 1), "-");
	}
	VERIFY_EQUAL(OPENMPT_API_VERSION_IS_PREREL, (std::string(OPENMPT_API_VERSION_PREREL).length() > 0) ? 1 : 0);
	
	VERIFY_EQUAL(fields["LIBOPENMPT_LTVER_CURRENT"].length() > 0, true);
	VERIFY_EQUAL(fields["LIBOPENMPT_LTVER_REVISION"].length() > 0, true);
	VERIFY_EQUAL(fields["LIBOPENMPT_LTVER_AGE"].length() > 0, true);
#endif // !MPT_OS_DJGPP
#endif // MPT_TEST_HAS_FILESYSTEM
#endif // LIBOPENMPT_BUILD

}


// Test if data types are interpreted correctly
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestTypes()
{

	static_assert(sizeof(std::uintptr_t) == sizeof(void*));
	#if defined(__SIZEOF_POINTER__)
		static_assert(__SIZEOF_POINTER__ == mpt::pointer_size);
		static_assert(__SIZEOF_POINTER__ == sizeof(void*));
	#endif

	VERIFY_EQUAL(int8_min, std::numeric_limits<int8>::min());
	VERIFY_EQUAL(int8_max, std::numeric_limits<int8>::max());
	VERIFY_EQUAL(uint8_max, std::numeric_limits<uint8>::max());

	VERIFY_EQUAL(int16_min, std::numeric_limits<int16>::min());
	VERIFY_EQUAL(int16_max, std::numeric_limits<int16>::max());
	VERIFY_EQUAL(uint16_max, std::numeric_limits<uint16>::max());

	VERIFY_EQUAL(int32_min, std::numeric_limits<int32>::min());
	VERIFY_EQUAL(int32_max, std::numeric_limits<int32>::max());
	VERIFY_EQUAL(uint32_max, std::numeric_limits<uint32>::max());

	VERIFY_EQUAL(int64_min, std::numeric_limits<int64>::min());
	VERIFY_EQUAL(int64_max, std::numeric_limits<int64>::max());
	VERIFY_EQUAL(uint64_max, std::numeric_limits<uint64>::max());


	static_assert(int8_max == std::numeric_limits<int8>::max());
	static_assert(uint8_max == std::numeric_limits<uint8>::max());

	static_assert(int16_max == std::numeric_limits<int16>::max());
	static_assert(uint16_max == std::numeric_limits<uint16>::max());

	static_assert(int32_max == std::numeric_limits<int32>::max());
	static_assert(uint32_max == std::numeric_limits<uint32>::max());

	static_assert(int64_max == std::numeric_limits<int64>::max());
	static_assert(uint64_max == std::numeric_limits<uint64>::max());

}



#ifdef MODPLUG_TRACKER

// In tracker debug builds, the sprintf-like function is retained in order to be able to validate our own formatting against sprintf.

// There are 4 reasons why this is not available for library code:
//  1. printf-like functionality is not type-safe.
//  2. There are portability problems with char/wchar_t and the semantics of %s/%ls/%S .
//  3. There are portability problems with specifying format for 64bit integers.
//  4. Formatting of floating point values depends on the currently set C locale.
//     A library is not allowed to mock with that and thus cannot influence the behavior in this case.

template <typename T>
static std::string StringFormat(std::string format, T x)
{
	#if MPT_COMPILER_MSVC
		// Count the needed array size.
		const size_t nCount = _scprintf(format.c_str(), x); // null character not included.
		std::vector<char> buf(nCount + 1); // + 1 is for null terminator.
		sprintf_s(&(buf[0]), buf.size(), format.c_str(), x);
		return &(buf[0]);
	#else
		int size = snprintf(NULL, 0, format.c_str(), x); // get required size, requires c99 compliant snprintf which msvc does not have
		std::vector<char> temp(size + 1);
		snprintf(&(temp[0]), size + 1, format.c_str(), x);
		return &(temp[0]);
	#endif
}

#endif

template <typename Tfloat>
static void TestFloatFormat(Tfloat x, std::string format, mpt::format_simple_flags f, std::size_t width = 0, int precision = -1)
{
#ifdef MODPLUG_TRACKER
	std::string str_sprintf = StringFormat(format, x);
#endif
	std::string str_iostreams = mpt::afmt::fmt(x, mpt::format_simple_spec<std::string>().SetFlags(f).SetWidth(width).SetPrecision(precision));
#ifdef MODPLUG_TRACKER
	//MPT_LOG_GLOBAL(LogDebug, "test", mpt::ToUnicode(mpt::Charset::ASCII, str_sprintf));
#endif
	//MPT_LOG_GLOBAL(LogDebug, "test", mpt::ToUnicode(mpt::Charset::ASCII, str_iostreams));
#ifdef MODPLUG_TRACKER
#if MPT_MSVC_AT_LEAST(2019,4) || MPT_GCC_AT_LEAST(11,1,0)
	// to_chars generates shortest form instead of 0-padded precision
	if(precision != -1)
#endif
	{
		VERIFY_EQUAL(str_iostreams, str_sprintf); // this will fail with a set c locale (and there is nothing that can be done about that in libopenmpt)
	}
#else
	MPT_UNREFERENCED_PARAMETER(format);
	MPT_UNUSED_VARIABLE(str_iostreams);
#endif
}


template <typename Tfloat>
static void TestFloatFormats(Tfloat x)
{

	TestFloatFormat(x, "%.8g", mpt::afmt::NotaNrm | mpt::afmt::FillOff, 0, 8);

	TestFloatFormat(x, MPT_AFORMAT("%.{}g")(std::numeric_limits<Tfloat>::max_digits10), mpt::afmt::NotaNrm | mpt::afmt::FillOff);
	TestFloatFormat(x, MPT_AFORMAT("%.{}f")(std::numeric_limits<Tfloat>::digits10), mpt::afmt::NotaFix | mpt::afmt::FillOff);
	TestFloatFormat(x, MPT_AFORMAT("%.{}e")(std::numeric_limits<Tfloat>::max_digits10 - 1), mpt::afmt::NotaSci | mpt::afmt::FillOff);

	TestFloatFormat(x, "%.0f", mpt::afmt::NotaFix | mpt::afmt::FillOff, 0, 0);
	TestFloatFormat(x, "%.1f", mpt::afmt::NotaFix | mpt::afmt::FillOff, 0, 1);
	TestFloatFormat(x, "%.2f", mpt::afmt::NotaFix | mpt::afmt::FillOff, 0, 2);
	TestFloatFormat(x, "%.3f", mpt::afmt::NotaFix | mpt::afmt::FillOff, 0, 3);
	TestFloatFormat(x, "%0.1f", mpt::afmt::NotaFix | mpt::afmt::FillNul, 0, 1);
	TestFloatFormat(x, "%02.0f", mpt::afmt::NotaFix | mpt::afmt::FillNul, 2, 0);
}



MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestStringFormatting()
{
	VERIFY_EQUAL(mpt::afmt::val(1.5f), "1.5");
	VERIFY_EQUAL(mpt::afmt::val(true), "1");
	VERIFY_EQUAL(mpt::afmt::val(false), "0");
	//VERIFY_EQUAL(mpt::afmt::val('A'), "A"); // deprecated
	//VERIFY_EQUAL(mpt::afmt::val(L'A'), "A"); // deprecated

	VERIFY_EQUAL(mpt::afmt::val(0), "0");
	VERIFY_EQUAL(mpt::afmt::val(-23), "-23");
	VERIFY_EQUAL(mpt::afmt::val(42), "42");

	VERIFY_EQUAL(mpt::afmt::hex0<3>((int32)-1), "-001");
	VERIFY_EQUAL(mpt::afmt::hex((int32)-1), "-1");
	VERIFY_EQUAL(mpt::afmt::hex(-0xabcde), "-abcde");
	VERIFY_EQUAL(mpt::afmt::hex(int32_min), "-80000000");
	VERIFY_EQUAL(mpt::afmt::hex(int32_min + 1), "-7fffffff");
	VERIFY_EQUAL(mpt::afmt::hex(0x123e), "123e");
	VERIFY_EQUAL(mpt::afmt::hex0<6>(0x123e), "00123e");
	VERIFY_EQUAL(mpt::afmt::hex0<2>(0x123e), "123e");

	VERIFY_EQUAL(mpt::afmt::dec0<0>(1), "1");
	VERIFY_EQUAL(mpt::afmt::dec0<1>(1), "1");
	VERIFY_EQUAL(mpt::afmt::dec0<2>(1), "01");
	VERIFY_EQUAL(mpt::afmt::dec0<3>(1), "001");
	VERIFY_EQUAL(mpt::afmt::dec0<0>(11), "11");
	VERIFY_EQUAL(mpt::afmt::dec0<1>(11), "11");
	VERIFY_EQUAL(mpt::afmt::dec0<2>(11), "11");
	VERIFY_EQUAL(mpt::afmt::dec0<3>(11), "011");
	VERIFY_EQUAL(mpt::afmt::dec0<0>(-1), "-1");
	VERIFY_EQUAL(mpt::afmt::dec0<1>(-1), "-1");
	VERIFY_EQUAL(mpt::afmt::dec0<2>(-1), "-01");
	VERIFY_EQUAL(mpt::afmt::dec0<3>(-1), "-001");

	VERIFY_EQUAL(mpt::ufmt::HEX0<7>(0xa2345678), U_("A2345678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<8>(0xa2345678), U_("A2345678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<9>(0xa2345678), U_("0A2345678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<10>(0xa2345678), U_("00A2345678"));

#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
	VERIFY_EQUAL(mpt::wfmt::hex(0x123e), L"123e");
	VERIFY_EQUAL(mpt::wfmt::hex0<6>(0x123e), L"00123e");
	VERIFY_EQUAL(mpt::wfmt::hex0<2>(0x123e), L"123e");
#endif

	VERIFY_EQUAL(mpt::afmt::val(-87.0f), "-87");
	if(mpt::afmt::val(-0.5e-6) != "-5e-007"
		&& mpt::afmt::val(-0.5e-6) != "-5e-07"
		&& mpt::afmt::val(-0.5e-6) != "-5e-7"
		&& mpt::afmt::val(-0.5e-6) != "-4.9999999999999998e-7"
		&& mpt::afmt::val(-0.5e-6) != "-4.9999999999999998e-07"
		&& mpt::afmt::val(-0.5e-6) != "-4.9999999999999998e-007"
		)
	{
		VERIFY_EQUAL(true, false);
	}
	if(mpt::afmt::val(-1.0 / 65536.0) != "-1.52587890625e-005"
		&& mpt::afmt::val(-1.0 / 65536.0) != "-1.52587890625e-05"
		&& mpt::afmt::val(-1.0 / 65536.0) != "-1.52587890625e-5"
		)
	{
		VERIFY_EQUAL(true, false);
	}
	if(mpt::afmt::val(-1.0f / 65536.0f) != "-1.52587891e-005"
		&& mpt::afmt::val(-1.0f / 65536.0f) != "-1.52587891e-05"
		&& mpt::afmt::val(-1.0f / 65536.0f) != "-1.52587891e-5"
		&& mpt::afmt::val(-1.0f / 65536.0f) != "-1.5258789e-005"
		&& mpt::afmt::val(-1.0f / 65536.0f) != "-1.5258789e-05"
		&& mpt::afmt::val(-1.0f / 65536.0f) != "-1.5258789e-5"
		)
	{
		VERIFY_EQUAL(true, false);
	}
	if(mpt::afmt::val(58.65403492763) != "58.654034927630001"
		&& mpt::afmt::val(58.65403492763) != "58.65403492763"
		)
	{
		VERIFY_EQUAL(true, false);
	}
	VERIFY_EQUAL(mpt::afmt::flt(58.65403492763, 6), "58.654");
	VERIFY_EQUAL(mpt::afmt::fix(23.42, 1), "23.4");
	VERIFY_EQUAL(mpt::afmt::fix(234.2, 1), "234.2");
	VERIFY_EQUAL(mpt::afmt::fix(2342.0, 1), "2342.0");
	
	VERIFY_EQUAL(mpt::afmt::dec(2, ";", 2345678), std::string("2;34;56;78"));
	VERIFY_EQUAL(mpt::afmt::dec(2, ";", 12345678), std::string("12;34;56;78"));
	VERIFY_EQUAL(mpt::afmt::hex(3, ":", 0xa2345678), std::string("a2:345:678"));

	VERIFY_EQUAL(mpt::ufmt::dec(2, U_(";"), 12345678), U_("12;34;56;78"));
	VERIFY_EQUAL(mpt::ufmt::hex(3, U_(":"), 0xa2345678), U_("a2:345:678"));

	VERIFY_EQUAL(mpt::ufmt::HEX0<7>(3, U_(":"), 0xa2345678), U_("A2:345:678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<8>(3, U_(":"), 0xa2345678), U_("A2:345:678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<9>(3, U_(":"), 0xa2345678), U_("0A2:345:678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<10>(3, U_(":"), 0xa2345678), U_("0:0A2:345:678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<11>(3, U_(":"), 0xa2345678), U_("00:0A2:345:678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<12>(3, U_(":"), 0xa2345678), U_("000:0A2:345:678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<7>(3, U_(":"), -0x12345678), U_("-12:345:678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<8>(3, U_(":"), -0x12345678), U_("-12:345:678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<9>(3, U_(":"), -0x12345678), U_("-012:345:678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<10>(3, U_(":"), -0x12345678), U_("-0:012:345:678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<11>(3, U_(":"), -0x12345678), U_("-00:012:345:678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<12>(3, U_(":"), -0x12345678), U_("-000:012:345:678"));

	VERIFY_EQUAL(mpt::ufmt::HEX0<5>(3, U_(":"), 0x345678), U_("345:678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<6>(3, U_(":"), 0x345678), U_("345:678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<7>(3, U_(":"), 0x345678), U_("0:345:678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<5>(3, U_(":"), -0x345678), U_("-345:678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<6>(3, U_(":"), -0x345678), U_("-345:678"));
	VERIFY_EQUAL(mpt::ufmt::HEX0<7>(3, U_(":"), -0x345678), U_("-0:345:678"));

	VERIFY_EQUAL(mpt::afmt::left(3, "a"), "a  ");
	VERIFY_EQUAL(mpt::afmt::right(3, "a"), "  a");
	VERIFY_EQUAL(mpt::afmt::center(3, "a"), " a ");
	VERIFY_EQUAL(mpt::afmt::center(4, "a"), " a  ");

	#if defined(MPT_WITH_MFC)
		VERIFY_EQUAL(mpt::cfmt::left(3, CString(_T("a"))), CString(_T("a  ")));
		VERIFY_EQUAL(mpt::cfmt::right(3, CString(_T("a"))), CString(_T("  a")));
		VERIFY_EQUAL(mpt::cfmt::center(3, CString(_T("a"))), CString(_T(" a ")));
		VERIFY_EQUAL(mpt::cfmt::center(4, CString(_T("a"))), CString(_T(" a  ")));
	#endif // MPT_WITH_MFC

	VERIFY_EQUAL(mpt::parse_or<int>("", -1), -1);
	VERIFY_EQUAL(mpt::parse_or<int>("0", -1), 0);

	VERIFY_EQUAL(mpt::parse<bool>("1"), true);
	VERIFY_EQUAL(mpt::parse<bool>("0"), false);
	VERIFY_EQUAL(mpt::parse<bool>("2"), true);
	VERIFY_EQUAL(mpt::parse<bool>("-0"), false);
	VERIFY_EQUAL(mpt::parse<bool>("-1"), true);

	VERIFY_EQUAL(mpt::parse<uint32>("586"), 586u);
	VERIFY_EQUAL(mpt::parse<uint32>("2147483647"), (uint32)int32_max);
	VERIFY_EQUAL(mpt::parse<uint32>("4294967295"), uint32_max);

	VERIFY_EQUAL(mpt::parse<int64>("-9223372036854775808"), int64_min);
	VERIFY_EQUAL(mpt::parse<int64>("-159"), -159);
	VERIFY_EQUAL(mpt::parse<int64>("9223372036854775807"), int64_max);

	VERIFY_EQUAL(mpt::parse<uint64>("85059"), 85059u);
	VERIFY_EQUAL(mpt::parse<uint64>("9223372036854775807"), (uint64)int64_max);
	VERIFY_EQUAL(mpt::parse<uint64>("18446744073709551615"), uint64_max);

	VERIFY_EQUAL(mpt::parse<float>("-87.0"), -87.0f);
#if !MPT_OS_DJGPP
	VERIFY_EQUAL(mpt::parse<double>("-0.5e-6"), -0.5e-6);
#endif
#if !MPT_OS_DJGPP
	VERIFY_EQUAL(mpt::parse<double>("58.65403492763"), 58.65403492763);
#else
	VERIFY_EQUAL_EPS(mpt::parse<double>("58.65403492763"), 58.65403492763, 0.0001);
#endif

	VERIFY_EQUAL(mpt::parse<float>(mpt::afmt::val(-87.0)), -87.0f);
	// VS2022 17.7.2 parses "-5e-07" as -5.0000000000000004e-06 instead of -4.9999999999999998e-07 which is closer
	// <https://developercommunity.visualstudio.com/t/Parsing-double-from-stringstream-returns/10450694>
	// <https://developercommunity.visualstudio.com/t/v143-1437-istringstream-incorrectly-con/10450662>
#if !MPT_OS_DJGPP && !(MPT_MSVC_AT_LEAST(2022, 7) && MPT_MSVC_BEFORE(2022, 9))
	VERIFY_EQUAL(mpt::parse<double>(mpt::afmt::val(-0.5e-6)), -0.5e-6);
#endif

	VERIFY_EQUAL(mpt::parse_hex<unsigned char>("fe"), 254);
#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
	VERIFY_EQUAL(mpt::parse_hex<unsigned char>(L"fe"), 254);
#endif
	VERIFY_EQUAL(mpt::parse_hex<unsigned int>(U_("ffff")), 65535);

	TestFloatFormats(0.0f);
	TestFloatFormats(-0.0f);
	TestFloatFormats(1.0f);
	TestFloatFormats(-1.0f);
	TestFloatFormats(0.1f);
	TestFloatFormats(-0.1f);
	TestFloatFormats(1000000000.0f);
	TestFloatFormats(-1000000000.0f);
	TestFloatFormats(0.0000000001f);
	TestFloatFormats(-0.0000000001f);
	TestFloatFormats(6.12345f);

	TestFloatFormats(0.0);
	TestFloatFormats(-0.0);
	TestFloatFormats(1.0);
	TestFloatFormats(-1.0);
	TestFloatFormats(0.1);
	TestFloatFormats(-0.1);
	TestFloatFormats(1000000000.0);
	TestFloatFormats(-1000000000.0);
	TestFloatFormats(0.0000000001);
	TestFloatFormats(-0.0000000001);
	TestFloatFormats(6.12345);

	TestFloatFormats(42.1234567890);
	TestFloatFormats(0.1234567890);
	TestFloatFormats(1234567890000000.0);
	TestFloatFormats(0.0000001234567890);

	TestFloatFormats(mpt::numbers::pi);
	TestFloatFormats(3.14159265358979323846);
	TestFloatFormats(3.14159265358979323846f);

	VERIFY_EQUAL(mpt::afmt::flt(6.12345, 3), "6.12");
	VERIFY_EQUAL(mpt::afmt::fix(6.12345, 3), "6.123");
	VERIFY_EQUAL(mpt::afmt::flt(6.12345, 4), "6.123");
	VERIFY_EQUAL(mpt::afmt::fix(6.12345, 4), "6.1235");

#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
	VERIFY_EQUAL(mpt::wfmt::flt(6.12345, 3), L"6.12");
	VERIFY_EQUAL(mpt::wfmt::fix(6.12345, 3), L"6.123");
	VERIFY_EQUAL(mpt::wfmt::flt(6.12345, 4), L"6.123");
#endif

	static_assert(mpt::parse_format_string_argument_count("") == 0);
	static_assert(mpt::parse_format_string_argument_count("{{") == 0);
	static_assert(mpt::parse_format_string_argument_count("}}") == 0);
	static_assert(mpt::parse_format_string_argument_count("{}") == 1);
	static_assert(mpt::parse_format_string_argument_count("{}{}") == 2);
	static_assert(mpt::parse_format_string_argument_count("{0}{1}") == 2);

	// basic
	VERIFY_EQUAL(MPT_AFORMAT("{}{}{}")(1,2,3), "123");
	VERIFY_EQUAL(MPT_AFORMAT("{2}{1}{0}")(1,2,3), "321");

	VERIFY_EQUAL(MPT_AFORMAT("{2}{1}{0}{4}{3}{6}{5}{7}{10}{9}{8}")(0,1,2,3,4,5,6,7,8,9,"a"), "21043657a98");

	//VERIFY_EQUAL(MPT_AFORMAT("{2}{1}{0}{2}{1}{0}{10}{9}{8}")(0,1,2,3,4,5,6,7,8,9,"a"), "210210a98");

#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
	VERIFY_EQUAL(MPT_WFORMAT("{}{}{}")(1,2,3), L"123");
#endif

	// escaping behviour
	VERIFY_EQUAL(MPT_AFORMAT("%")(), "%");
	VERIFY_EQUAL(MPT_AFORMAT("%")(), "%");
	VERIFY_EQUAL(MPT_AFORMAT("%%")(), "%%");
	VERIFY_EQUAL(MPT_AFORMAT("{}")("a"), "a");
	VERIFY_EQUAL(MPT_AFORMAT("{}%")("a"), "a%");
	VERIFY_EQUAL(MPT_AFORMAT("{}%")("a"), "a%");
	VERIFY_EQUAL(MPT_AFORMAT("{}%%")("a"), "a%%");
	VERIFY_EQUAL(MPT_AFORMAT("%1")(), "%1");
	VERIFY_EQUAL(MPT_AFORMAT("%{}")("a"), "%a");
	VERIFY_EQUAL(MPT_AFORMAT("%b")(), "%b");
	VERIFY_EQUAL(MPT_AFORMAT("{{}}")(), "{}");
	VERIFY_EQUAL(MPT_AFORMAT("{{{}}}")("a"), "{a}");

#if defined(MPT_WITH_MFC)
	VERIFY_EQUAL(mpt::ufmt::val(CString(_T("foobar"))), U_("foobar"));
	VERIFY_EQUAL(mpt::ufmt::val(CString(_T("foobar"))), U_("foobar"));
	VERIFY_EQUAL(MPT_CFORMAT("{}{}{}")(1,2,3), _T("123"));
	VERIFY_EQUAL(MPT_CFORMAT("{}{}{}")(1,mpt::cfmt::dec0<3>(2),3), _T("10023"));
#endif // MPT_WITH_MFC

	FlagSet<MODTYPE> foo;
	foo.set(MOD_TYPE_MOD, true);
	VERIFY_EQUAL(MPT_UFORMAT("{}")(foo), U_("00000000000000000000000000000001"));

}


static int64 TestDate1(int s, int m, int h, unsigned int D, unsigned int M, int Y) {
	return mpt::chrono::default_system_clock::to_unix_seconds(mpt::Date::default_from_UTC(mpt::Date::UTC{Y,M,D,h,m,s}));
}

static mpt::Date::UTC TestDate2(int s, int m, int h, unsigned int D, unsigned int M, int Y) {
	return mpt::Date::UTC{Y,M,D,h,m,s};
}


MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestMisc1()
{

	VERIFY_EQUAL(ModCommand::IsPcNote(NOTE_MAX), false);
	VERIFY_EQUAL(ModCommand::IsPcNote(NOTE_PC), true);
	VERIFY_EQUAL(ModCommand::IsPcNote(NOTE_PCS), true);

	VERIFY_EQUAL(CModSpecifications::ExtensionToType(U_(".mod")), MOD_TYPE_MOD);
	VERIFY_EQUAL(CModSpecifications::ExtensionToType(U_("mod")), MOD_TYPE_MOD);
	VERIFY_EQUAL(CModSpecifications::ExtensionToType(U_(".s3m")), MOD_TYPE_S3M);
	VERIFY_EQUAL(CModSpecifications::ExtensionToType(U_("s3m")), MOD_TYPE_S3M);
	VERIFY_EQUAL(CModSpecifications::ExtensionToType(U_(".xm")), MOD_TYPE_XM);
	VERIFY_EQUAL(CModSpecifications::ExtensionToType(U_("xm")), MOD_TYPE_XM);
	VERIFY_EQUAL(CModSpecifications::ExtensionToType(U_(".it")), MOD_TYPE_IT);
	VERIFY_EQUAL(CModSpecifications::ExtensionToType(U_("it")), MOD_TYPE_IT);
	VERIFY_EQUAL(CModSpecifications::ExtensionToType(U_(".itp")), MOD_TYPE_NONE);
	VERIFY_EQUAL(CModSpecifications::ExtensionToType(U_("itp")), MOD_TYPE_NONE);
	VERIFY_EQUAL(CModSpecifications::ExtensionToType(U_("mptm")), MOD_TYPE_MPT);
	VERIFY_EQUAL(CModSpecifications::ExtensionToType(U_("invalidExtension")), MOD_TYPE_NONE);
	VERIFY_EQUAL(CModSpecifications::ExtensionToType(U_("ita")), MOD_TYPE_NONE);
	VERIFY_EQUAL(CModSpecifications::ExtensionToType(U_("s2m")), MOD_TYPE_NONE);
	VERIFY_EQUAL(CModSpecifications::ExtensionToType(U_("")), MOD_TYPE_NONE);

	// invalid
	VERIFY_EQUAL(SampleFormat::FromInt(0), SampleFormat::Default);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0000'11'0), SampleFormat::Default);

	// correct
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0001'00'1), SampleFormat::Unsigned8);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0001'00'0), SampleFormat::Int8);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0010'00'0), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0011'00'0), SampleFormat::Int24);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0100'00'0), SampleFormat::Int32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0100'00'0), SampleFormat::Float32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'1000'00'0), SampleFormat::Float64);

	// no size
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0000'00'0), SampleFormat::Default);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0000'00'1), SampleFormat::Unsigned8);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0000'00'0), SampleFormat::Float32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0000'00'1), SampleFormat::Default);

	// invalid unsigned
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0010'00'1), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0011'00'1), SampleFormat::Int24);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0100'00'1), SampleFormat::Int32);

	// invalid float
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0001'00'0), SampleFormat::Int8);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0010'00'0), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0011'00'0), SampleFormat::Int24);

	// bogus size

	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0001'00'0), SampleFormat::Int8);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0010'00'0), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0011'00'0), SampleFormat::Int24);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0100'00'0), SampleFormat::Int32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0101'00'0), SampleFormat::Int32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0110'00'0), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0111'00'0), SampleFormat::Int24);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'1000'00'0), SampleFormat::Float64);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'1001'00'0), SampleFormat::Float64);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'1010'00'0), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'1011'00'0), SampleFormat::Int24);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'1100'00'0), SampleFormat::Int32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'1101'00'0), SampleFormat::Int32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'1110'00'0), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'1111'00'0), SampleFormat::Int24);

	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0001'00'1), SampleFormat::Unsigned8);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0010'00'1), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0011'00'1), SampleFormat::Int24);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0100'00'1), SampleFormat::Int32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0101'00'1), SampleFormat::Int32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0110'00'1), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'0111'00'1), SampleFormat::Int24);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'1000'00'1), SampleFormat::Float64);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'1001'00'1), SampleFormat::Float64);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'1010'00'1), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'1011'00'1), SampleFormat::Int24);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'1100'00'1), SampleFormat::Int32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'1101'00'1), SampleFormat::Int32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'1110'00'1), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b0'1111'00'1), SampleFormat::Int24);

	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0001'00'0), SampleFormat::Int8);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0010'00'0), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0011'00'0), SampleFormat::Int24);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0100'00'0), SampleFormat::Float32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0101'00'0), SampleFormat::Float32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0110'00'0), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0111'00'0), SampleFormat::Int24);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'1000'00'0), SampleFormat::Float64);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'1001'00'0), SampleFormat::Float64);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'1010'00'0), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'1011'00'0), SampleFormat::Int24);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'1100'00'0), SampleFormat::Float32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'1101'00'0), SampleFormat::Float32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'1110'00'0), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'1111'00'0), SampleFormat::Int24);

	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0001'00'1), SampleFormat::Unsigned8);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0010'00'1), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0011'00'1), SampleFormat::Int24);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0100'00'1), SampleFormat::Float32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0101'00'1), SampleFormat::Float32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0110'00'1), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'0111'00'1), SampleFormat::Int24);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'1000'00'1), SampleFormat::Float64);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'1001'00'1), SampleFormat::Float64);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'1010'00'1), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'1011'00'1), SampleFormat::Int24);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'1100'00'1), SampleFormat::Float32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'1101'00'1), SampleFormat::Float32);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'1110'00'1), SampleFormat::Int16);
	VERIFY_EQUAL(SampleFormat::FromInt(0b1'1111'00'1), SampleFormat::Int24);

}


MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestMisc2()
{

	// Check for completeness of supported effect list in mod specifications
	for(const auto &spec : ModSpecs::Collection)
	{
		VERIFY_EQUAL(strlen(spec->commands), (size_t)MAX_EFFECTS);
		VERIFY_EQUAL(strlen(spec->volcommands), (size_t)MAX_VOLCMDS);
	}

#ifdef MODPLUG_TRACKER

	{
		std::vector<std::byte> data;
		data.push_back(mpt::as_byte(0));
		data.push_back(mpt::as_byte(255));
		data.push_back(mpt::as_byte(1));
		data.push_back(mpt::as_byte(2));
		mpt::PathString fn = GetTempFilenameBase() + P_("lazy");
		RemoveFile(fn);
		mpt::IO::FileRef f(fn);
		f = data;
		std::vector<std::byte> data2;
		data2 = f;
		VERIFY_EQUAL(data.size(), data2.size());
		for(std::size_t i = 0; i < data.size() && i < data2.size(); ++i)
		{
			VERIFY_EQUAL(data[i], data2[i]);
		}
		RemoveFile(fn);
	}

#endif // MODPLUG_TRACKER

#ifdef MPT_WITH_ZLIB
	VERIFY_EQUAL(crc32(0, mpt::byte_cast<const unsigned char*>(std::string("123456789").c_str()), 9), 0xCBF43926u);
#endif
#ifdef MPT_WITH_MINIZ
	VERIFY_EQUAL(mz_crc32(0, mpt::byte_cast<const unsigned char*>(std::string("123456789").c_str()), 9), 0xCBF43926u);
#endif

	// Check floating-point accuracy in TransposeToFrequency
	static constexpr int32 transposeToFrequency[] =
	{
		      5,       5,       5,       5,
		     31,      32,      33,      34,
		    196,     202,     207,     214,
		   1243,    1280,    1317,    1356,
		   7894,    8125,    8363,    8608,
		  50121,   51590,   53102,   54658,
		 318251,  327576,  337175,  347055,
		2020767, 2079980, 2140928, 2203663,
	};

	int freqIndex = 0;
	for(int32 transpose = -128; transpose < 128; transpose += 32)
	{
		for(int32 finetune = -128; finetune < 128; finetune += 64, freqIndex++)
		{
			const auto freq = ModSample::TransposeToFrequency(transpose, finetune);
			VERIFY_EQUAL_EPS(transposeToFrequency[freqIndex], static_cast<int32>(freq), 1);
			if(transpose >= -96)
			{
				// Verify transpose+finetune <-> frequency roundtrip
				// (not for transpose = -128 because it would require fractional precision that we don't have here)
				ModSample smp;
				smp.nC5Speed = freq;
				smp.FrequencyToTranspose();
				smp.TransposeToFrequency();
				VERIFY_EQUAL(freq, smp.nC5Speed);
			}
		}
	}

	{
		ModSample smp;
		smp.nC5Speed = 9999;
		smp.Transpose(1.5);
		VERIFY_EQUAL_EPS(28281, static_cast<int32>(smp.nC5Speed), 1);
		smp.Transpose(-1.3);
		VERIFY_EQUAL_EPS(11486, static_cast<int32>(smp.nC5Speed), 1);
	}

	// Check SamplePosition fixed-point type
	VERIFY_EQUAL(SamplePosition(1).GetRaw(), 1);
	VERIFY_EQUAL(SamplePosition(2).Set(1), SamplePosition(1, 0));
	VERIFY_EQUAL(SamplePosition(2).SetInt(1), SamplePosition(1, 2));
	VERIFY_EQUAL(SamplePosition(1).IsPositive(), true);
	VERIFY_EQUAL(SamplePosition(0).IsZero(), true);
	VERIFY_EQUAL(SamplePosition(1, 0).IsUnity(), true);
	VERIFY_EQUAL(SamplePosition(1, 1).IsUnity(), false);
	VERIFY_EQUAL(SamplePosition(-1).IsNegative(), true);
	VERIFY_EQUAL(SamplePosition(int64_max).GetRaw(), int64_max);
	VERIFY_EQUAL(SamplePosition(2, SamplePosition::fractMax).GetInt(), 2);
	VERIFY_EQUAL(SamplePosition(2, SamplePosition::fractMax).GetFract(), SamplePosition::fractMax);
	VERIFY_EQUAL(SamplePosition(1, SamplePosition::fractMax).GetInvertedFract(), SamplePosition(0, 1));
	VERIFY_EQUAL(SamplePosition(1, 0).GetInvertedFract(), SamplePosition(1, 0));
	VERIFY_EQUAL(SamplePosition(2, 0).Negate(), SamplePosition(-2, 0));
	VERIFY_EQUAL(SamplePosition::Ratio(10, 5), SamplePosition(2, 0));
	VERIFY_EQUAL(SamplePosition(1, 1) + SamplePosition(2, 2), SamplePosition(3, 3));
	VERIFY_EQUAL(SamplePosition(1, 0) * 3, SamplePosition(3, 0));
	VERIFY_EQUAL((SamplePosition(6, 0) / SamplePosition(2, 0)), 3);
	
	VERIFY_EQUAL(srlztn::ID::FromInt(static_cast<uint32>(0x87654321u)), srlztn::ID("\x21\x43\x65\x87"));

#if defined(MODPLUG_TRACKER)

	VERIFY_EQUAL(mpt::osinfo::windows::wine::VersionContext::VersionAsString(mpt::osinfo::windows::wine::VersionContext::ParseVersion("1.1.44" )), "1.1.44");
	VERIFY_EQUAL(mpt::osinfo::windows::wine::VersionContext::VersionAsString(mpt::osinfo::windows::wine::VersionContext::ParseVersion("1.6.2"  )), "1.6.2" );
	VERIFY_EQUAL(mpt::osinfo::windows::wine::VersionContext::VersionAsString(mpt::osinfo::windows::wine::VersionContext::ParseVersion("1.8"    )), "1.8.0" );
	VERIFY_EQUAL(mpt::osinfo::windows::wine::VersionContext::VersionAsString(mpt::osinfo::windows::wine::VersionContext::ParseVersion("2.0-rc" )), "2.0.0" );
	VERIFY_EQUAL(mpt::osinfo::windows::wine::VersionContext::VersionAsString(mpt::osinfo::windows::wine::VersionContext::ParseVersion("2.0-rc4")), "2.0.0" );
	VERIFY_EQUAL(mpt::osinfo::windows::wine::VersionContext::VersionAsString(mpt::osinfo::windows::wine::VersionContext::ParseVersion("2.0"    )), "2.0.0" );
	VERIFY_EQUAL(mpt::osinfo::windows::wine::VersionContext::VersionAsString(mpt::osinfo::windows::wine::VersionContext::ParseVersion("2.4"    )), "2.4.0" );

#endif // MODPLUG_TRACKER

	// date

	VERIFY_EQUAL(             0, TestDate1(  0,  0,  0,  1,  1, 1970 ));
	VERIFY_EQUAL(          3600, TestDate1(  0,  0,  1,  1,  1, 1970 ));
	VERIFY_EQUAL(         86400, TestDate1(  0,  0,  0,  2,  1, 1970 ));
	VERIFY_EQUAL(      31536000, TestDate1(  0,  0,  0,  1,  1, 1971 ));
	VERIFY_EQUAL(     100000000, TestDate1( 40, 46,  9,  3,  3, 1973 ));
	VERIFY_EQUAL(     951782400, TestDate1(  0,  0,  0, 29,  2, 2000 ));
	VERIFY_EQUAL(    1000000000, TestDate1( 40, 46,  1,  9,  9, 2001 ));
	VERIFY_EQUAL(    1044057600, TestDate1(  0,  0,  0,  1,  2, 2003 ));
	VERIFY_EQUAL(    1044144000, TestDate1(  0,  0,  0,  2,  2, 2003 ));
	VERIFY_EQUAL(    1046476800, TestDate1(  0,  0,  0,  1,  3, 2003 ));
	VERIFY_EQUAL(    1064966400, TestDate1(  0,  0,  0,  1, 10, 2003 ));
	VERIFY_EQUAL(    1077926399, TestDate1( 59, 59, 23, 27,  2, 2004 ));
	VERIFY_EQUAL(    1077926400, TestDate1(  0,  0,  0, 28,  2, 2004 ));
	VERIFY_EQUAL(    1077926410, TestDate1( 10,  0,  0, 28,  2, 2004 ));
	VERIFY_EQUAL(    1078012799, TestDate1( 59, 59, 23, 28,  2, 2004 ));
	VERIFY_EQUAL(    1078012800, TestDate1(  0,  0,  0, 29,  2, 2004 ));
	VERIFY_EQUAL(    1078012820, TestDate1( 20,  0,  0, 29,  2, 2004 ));
	VERIFY_EQUAL(    1078099199, TestDate1( 59, 59, 23, 29,  2, 2004 ));
	VERIFY_EQUAL(    1078099200, TestDate1(  0,  0,  0,  1,  3, 2004 ));
	VERIFY_EQUAL(    1078099230, TestDate1( 30,  0,  0,  1,  3, 2004 ));
	VERIFY_EQUAL(    1078185599, TestDate1( 59, 59, 23,  1,  3, 2004 ));
	VERIFY_EQUAL(    1096588800, TestDate1(  0,  0,  0,  1, 10, 2004 ));
	VERIFY_EQUAL(    1413064016, TestDate1( 56, 46, 21, 11, 10, 2014 ));
	VERIFY_EQUAL(    1413064100, TestDate1( 20, 48, 21, 11, 10, 2014 ));

	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(             0)), TestDate2(  0,  0,  0,  1,  1, 1970 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(          3600)), TestDate2(  0,  0,  1,  1,  1, 1970 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(         86400)), TestDate2(  0,  0,  0,  2,  1, 1970 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(      31536000)), TestDate2(  0,  0,  0,  1,  1, 1971 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(     100000000)), TestDate2( 40, 46,  9,  3,  3, 1973 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(     951782400)), TestDate2(  0,  0,  0, 29,  2, 2000 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1000000000)), TestDate2( 40, 46,  1,  9,  9, 2001 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1044057600)), TestDate2(  0,  0,  0,  1,  2, 2003 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1044144000)), TestDate2(  0,  0,  0,  2,  2, 2003 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1046476800)), TestDate2(  0,  0,  0,  1,  3, 2003 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1064966400)), TestDate2(  0,  0,  0,  1, 10, 2003 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1077926399)), TestDate2( 59, 59, 23, 27,  2, 2004 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1077926400)), TestDate2(  0,  0,  0, 28,  2, 2004 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1077926410)), TestDate2( 10,  0,  0, 28,  2, 2004 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1078012799)), TestDate2( 59, 59, 23, 28,  2, 2004 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1078012800)), TestDate2(  0,  0,  0, 29,  2, 2004 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1078012820)), TestDate2( 20,  0,  0, 29,  2, 2004 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1078099199)), TestDate2( 59, 59, 23, 29,  2, 2004 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1078099200)), TestDate2(  0,  0,  0,  1,  3, 2004 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1078099230)), TestDate2( 30,  0,  0,  1,  3, 2004 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1078185599)), TestDate2( 59, 59, 23,  1,  3, 2004 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1096588800)), TestDate2(  0,  0,  0,  1, 10, 2004 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1413064016)), TestDate2( 56, 46, 21, 11, 10, 2014 ));
	VERIFY_EQUAL(mpt::Date::UTC_from_default(mpt::chrono::default_system_clock::from_unix_seconds(    1413064100)), TestDate2( 20, 48, 21, 11, 10, 2014 ));


#ifdef MODPLUG_TRACKER

	// URI & HTTP

	{
		URI uri = ParseURI(U_("scheme://username:password@host:port/path?query#fragment"));
		VERIFY_EQUAL(uri.scheme, U_("scheme"));
		VERIFY_EQUAL(uri.username, U_("username"));
		VERIFY_EQUAL(uri.password, U_("password"));
		VERIFY_EQUAL(uri.host, U_("host"));
		VERIFY_EQUAL(uri.port, U_("port"));
		VERIFY_EQUAL(uri.path, U_("/path"));
		VERIFY_EQUAL(uri.query, U_("query"));
		VERIFY_EQUAL(uri.fragment, U_("fragment"));
	}
	{
		URI uri = ParseURI(U_("scheme://host/path"));
		VERIFY_EQUAL(uri.scheme, U_("scheme"));
		VERIFY_EQUAL(uri.username, U_(""));
		VERIFY_EQUAL(uri.password, U_(""));
		VERIFY_EQUAL(uri.host, U_("host"));
		VERIFY_EQUAL(uri.port, U_(""));
		VERIFY_EQUAL(uri.path, U_("/path"));
		VERIFY_EQUAL(uri.query, U_(""));
		VERIFY_EQUAL(uri.fragment, U_(""));
	}
	{
		URI uri = ParseURI(U_("scheme://host"));
		VERIFY_EQUAL(uri.scheme, U_("scheme"));
		VERIFY_EQUAL(uri.username, U_(""));
		VERIFY_EQUAL(uri.password, U_(""));
		VERIFY_EQUAL(uri.host, U_("host"));
		VERIFY_EQUAL(uri.port, U_(""));
		VERIFY_EQUAL(uri.path, U_(""));
		VERIFY_EQUAL(uri.query, U_(""));
		VERIFY_EQUAL(uri.fragment, U_(""));
	}
	{
		URI uri = ParseURI(U_("scheme://host?query"));
		VERIFY_EQUAL(uri.scheme, U_("scheme"));
		VERIFY_EQUAL(uri.username, U_(""));
		VERIFY_EQUAL(uri.password, U_(""));
		VERIFY_EQUAL(uri.host, U_("host"));
		VERIFY_EQUAL(uri.port, U_(""));
		VERIFY_EQUAL(uri.path, U_(""));
		VERIFY_EQUAL(uri.query, U_("query"));
		VERIFY_EQUAL(uri.fragment, U_(""));
	}
	{
		URI uri = ParseURI(U_("scheme://host#fragment"));
		VERIFY_EQUAL(uri.scheme, U_("scheme"));
		VERIFY_EQUAL(uri.username, U_(""));
		VERIFY_EQUAL(uri.password, U_(""));
		VERIFY_EQUAL(uri.host, U_("host"));
		VERIFY_EQUAL(uri.port, U_(""));
		VERIFY_EQUAL(uri.path, U_(""));
		VERIFY_EQUAL(uri.query, U_(""));
		VERIFY_EQUAL(uri.fragment, U_("fragment"));
	}
	{
		URI uri = ParseURI(U_("scheme://host?#"));
		VERIFY_EQUAL(uri.scheme, U_("scheme"));
		VERIFY_EQUAL(uri.username, U_(""));
		VERIFY_EQUAL(uri.password, U_(""));
		VERIFY_EQUAL(uri.host, U_("host"));
		VERIFY_EQUAL(uri.port, U_(""));
		VERIFY_EQUAL(uri.path, U_(""));
		VERIFY_EQUAL(uri.query, U_(""));
		VERIFY_EQUAL(uri.fragment, U_(""));
	}
	{
		URI uri = ParseURI(U_("scheme://host#?"));
		VERIFY_EQUAL(uri.scheme, U_("scheme"));
		VERIFY_EQUAL(uri.username, U_(""));
		VERIFY_EQUAL(uri.password, U_(""));
		VERIFY_EQUAL(uri.host, U_("host"));
		VERIFY_EQUAL(uri.port, U_(""));
		VERIFY_EQUAL(uri.path, U_(""));
		VERIFY_EQUAL(uri.query, U_(""));
		VERIFY_EQUAL(uri.fragment, U_("?"));
	}
	{
		URI uri = ParseURI(U_("scheme://username:password@[2001:db8::1]:port/path?query#fragment"));
		VERIFY_EQUAL(uri.scheme, U_("scheme"));
		VERIFY_EQUAL(uri.username, U_("username"));
		VERIFY_EQUAL(uri.password, U_("password"));
		VERIFY_EQUAL(uri.host, U_("[2001:db8::1]"));
		VERIFY_EQUAL(uri.port, U_("port"));
		VERIFY_EQUAL(uri.path, U_("/path"));
		VERIFY_EQUAL(uri.query, U_("query"));
		VERIFY_EQUAL(uri.fragment, U_("fragment"));
	}

	{
		URI uri = ParseURI(U_("https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top"));
		VERIFY_EQUAL(uri.scheme, U_("https"));
		VERIFY_EQUAL(uri.username, U_("john.doe"));
		VERIFY_EQUAL(uri.password, U_(""));
		VERIFY_EQUAL(uri.host, U_("www.example.com"));
		VERIFY_EQUAL(uri.port, U_("123"));
		VERIFY_EQUAL(uri.path, U_("/forum/questions/"));
		VERIFY_EQUAL(uri.query, U_("tag=networking&order=newest"));
		VERIFY_EQUAL(uri.fragment, U_("top"));
	}
	{
		URI uri = ParseURI(U_("ldap://[2001:db8::7]/c=GB?objectClass?one"));
		VERIFY_EQUAL(uri.scheme, U_("ldap"));
		VERIFY_EQUAL(uri.username, U_(""));
		VERIFY_EQUAL(uri.password, U_(""));
		VERIFY_EQUAL(uri.host, U_("[2001:db8::7]"));
		VERIFY_EQUAL(uri.port, U_(""));
		VERIFY_EQUAL(uri.path, U_("/c=GB"));
		VERIFY_EQUAL(uri.query, U_("objectClass?one"));
		VERIFY_EQUAL(uri.fragment, U_(""));
	}
	{
		URI uri = ParseURI(U_("mailto:John.Doe@example.com"));
		VERIFY_EQUAL(uri.scheme, U_("mailto"));
		VERIFY_EQUAL(uri.username, U_(""));
		VERIFY_EQUAL(uri.password, U_(""));
		VERIFY_EQUAL(uri.host, U_(""));
		VERIFY_EQUAL(uri.port, U_(""));
		VERIFY_EQUAL(uri.path, U_("John.Doe@example.com"));
		VERIFY_EQUAL(uri.query, U_(""));
		VERIFY_EQUAL(uri.fragment, U_(""));
	}
	{
		URI uri = ParseURI(U_("news:comp.infosystems.www.servers.unix"));
		VERIFY_EQUAL(uri.scheme, U_("news"));
		VERIFY_EQUAL(uri.username, U_(""));
		VERIFY_EQUAL(uri.password, U_(""));
		VERIFY_EQUAL(uri.host, U_(""));
		VERIFY_EQUAL(uri.port, U_(""));
		VERIFY_EQUAL(uri.path, U_("comp.infosystems.www.servers.unix"));
		VERIFY_EQUAL(uri.query, U_(""));
		VERIFY_EQUAL(uri.fragment, U_(""));
	}
	{
		URI uri = ParseURI(U_("tel:+1-816-555-1212"));
		VERIFY_EQUAL(uri.scheme, U_("tel"));
		VERIFY_EQUAL(uri.username, U_(""));
		VERIFY_EQUAL(uri.password, U_(""));
		VERIFY_EQUAL(uri.host, U_(""));
		VERIFY_EQUAL(uri.port, U_(""));
		VERIFY_EQUAL(uri.path, U_("+1-816-555-1212"));
		VERIFY_EQUAL(uri.query, U_(""));
		VERIFY_EQUAL(uri.fragment, U_(""));
	}
	{
		URI uri = ParseURI(U_("telnet://192.0.2.16:80/"));
		VERIFY_EQUAL(uri.scheme, U_("telnet"));
		VERIFY_EQUAL(uri.username, U_(""));
		VERIFY_EQUAL(uri.password, U_(""));
		VERIFY_EQUAL(uri.host, U_("192.0.2.16"));
		VERIFY_EQUAL(uri.port, U_("80"));
		VERIFY_EQUAL(uri.path, U_("/"));
		VERIFY_EQUAL(uri.query, U_(""));
		VERIFY_EQUAL(uri.fragment, U_(""));
	}
	{
		URI uri = ParseURI(U_("urn:oasis:names:specification:docbook:dtd:xml:4.1.2"));
		VERIFY_EQUAL(uri.scheme, U_("urn"));
		VERIFY_EQUAL(uri.username, U_(""));
		VERIFY_EQUAL(uri.password, U_(""));
		VERIFY_EQUAL(uri.host, U_(""));
		VERIFY_EQUAL(uri.port, U_(""));
		VERIFY_EQUAL(uri.path, U_("oasis:names:specification:docbook:dtd:xml:4.1.2"));
		VERIFY_EQUAL(uri.query, U_(""));
		VERIFY_EQUAL(uri.fragment, U_(""));
	}

	{
		HTTP::Request req;
		req.SetURI(ParseURI(U_("https://host/path?a1=a&a2=b")));
		VERIFY_EQUAL(req.protocol, HTTP::Protocol::HTTPS);
		VERIFY_EQUAL(req.host, U_("host"));
		VERIFY_EQUAL(req.path, U_("/path"));
		VERIFY_EQUAL(req.query.size(), 2u);
		if(req.query.size() == 2)
		{
			VERIFY_EQUAL(req.query[0], std::make_pair(U_("a1"), U_("a")));
			VERIFY_EQUAL(req.query[1], std::make_pair(U_("a2"), U_("b")));
		}
	}
	{
		HTTP::Request req;
		req.SetURI(ParseURI(U_("https://host/")));
		VERIFY_EQUAL(req.protocol, HTTP::Protocol::HTTPS);
		VERIFY_EQUAL(req.host, U_("host"));
		VERIFY_EQUAL(req.path, U_("/"));
	}
	{
		// https://daniel.haxx.se/blog/2022/09/08/http-http-http-http-http-http-http/
		URI uri = ParseURI(U_("http://http://http://@http://http://?http://#http://"));
		// python urllib / RFC 3986
		VERIFY_EQUAL(uri.scheme, U_("http"));
		VERIFY_EQUAL(uri.username, U_(""));
		VERIFY_EQUAL(uri.password, U_(""));
		VERIFY_EQUAL(uri.host, U_("http"));
		VERIFY_EQUAL(uri.port, U_(""));
		VERIFY_EQUAL(uri.path, U_("//http://@http://http://"));
		VERIFY_EQUAL(uri.query, U_("http://"));
		VERIFY_EQUAL(uri.fragment, U_("http://"));
		// curl (broken)
		//VERIFY_EQUAL(uri.scheme, U_("http"));
		//VERIFY_EQUAL(uri.username, U_("http"));
		//VERIFY_EQUAL(uri.password, U_("//http://"));
		//VERIFY_EQUAL(uri.host, U_("http"));
		//VERIFY_EQUAL(uri.port, U_(""));
		//VERIFY_EQUAL(uri.path, U_("//http://"));
		//VERIFY_EQUAL(uri.query, U_("http://"));
		//VERIFY_EQUAL(uri.fragment, U_("http://"));
	}

#endif // MODPLUG_TRACKER

	// https://github.com/kripken/emscripten/issues/4251
	#if MPT_OS_EMSCRIPTEN
		volatile int transpose = 32;
		volatile int finetune = 0;
		float exp = (transpose * 128.0f + finetune) * (1.0f / (12.0f * 128.0f)); 
		float f  = ::powf(2.0f,         exp);
		double d = ::pow (2.0 , (double)exp);
		VERIFY_EQUAL_EPS(d, 6.349605, 0.00001);
		VERIFY_EQUAL_EPS(f, 6.349605f, 0.00001f);
	#endif

}


MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestRandom()
{

	#ifdef FLAKY_TESTS

		mpt::default_prng& prng = *s_PRNG;

		{
			std::vector<std::size_t> hist(256);
			for(std::size_t i = 0; i < 256*256; ++i)
			{
				uint8 value = mpt::random<uint8>(prng);
				hist[value] += 1;
			}
			for(std::size_t i = 0; i < 256; ++i)
			{
				VERIFY_EQUAL_QUIET_NONCONT(mpt::is_in_range(hist[i], 16u, 65520u), true);
			}
		}
		{
			std::vector<std::size_t> hist(256);
			for(std::size_t i = 0; i < 256*256; ++i)
			{
				int8 value = mpt::random<int8>(prng);
				hist[static_cast<int>(value) + 0x80] += 1;
			}
			for(std::size_t i = 0; i < 256; ++i)
			{
				VERIFY_EQUAL_QUIET_NONCONT(mpt::is_in_range(hist[i], 16u, 65520u), true);
			}
		}
		{
			std::vector<std::size_t> hist(256);
			for(std::size_t i = 0; i < 256*256; ++i)
			{
				uint8 value = mpt::random<uint8>(prng, 1);
				hist[value] += 1;
			}
			for(std::size_t i = 0; i < 256; ++i)
			{
				if(i < 2)
				{
					VERIFY_EQUAL_QUIET_NONCONT(mpt::is_in_range(hist[i], 16u, 65520u), true);
				} else
				{
					VERIFY_EQUAL_QUIET_NONCONT(hist[i], 0u);
				}
			}
		}
	#endif
}


MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestPathNative()
{

	// Path splitting

#if MPT_OS_WINDOWS

	VERIFY_EQUAL(P_("").GetPrefix(), P_(""));
	VERIFY_EQUAL(P_("").GetDrive(), P_(""));
	VERIFY_EQUAL(P_("").GetDirectory(), P_(""));
	VERIFY_EQUAL(P_("").GetDirectoryWithDrive(), P_(""));
	VERIFY_EQUAL(P_("").GetFilenameBase(), P_(""));
	VERIFY_EQUAL(P_("").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_("").GetFilename(), P_(""));

	VERIFY_EQUAL(P_("C:\\").GetPrefix(), P_(""));
	VERIFY_EQUAL(P_("C:\\").GetDrive(), P_("C:"));
	VERIFY_EQUAL(P_("C:\\").GetDirectory(), P_("\\"));
	VERIFY_EQUAL(P_("C:\\").GetDirectoryWithDrive(), P_("C:\\"));
	VERIFY_EQUAL(P_("C:\\").GetFilenameBase(), P_(""));
	VERIFY_EQUAL(P_("C:\\").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_("C:\\").GetFilename(), P_(""));

	VERIFY_EQUAL(P_("\\directory\\").GetPrefix(), P_(""));
	VERIFY_EQUAL(P_("\\directory\\").GetDrive(), P_(""));
	VERIFY_EQUAL(P_("\\directory\\").GetDirectory(), P_("\\directory\\"));
	VERIFY_EQUAL(P_("\\directory\\").GetDirectoryWithDrive(), P_("\\directory\\"));
	VERIFY_EQUAL(P_("\\directory\\").GetFilenameBase(), P_(""));
	VERIFY_EQUAL(P_("\\directory\\").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_("\\directory\\").GetFilename(), P_(""));

	VERIFY_EQUAL(P_("\\directory\\file.txt").GetPrefix(), P_(""));
	VERIFY_EQUAL(P_("\\directory\\file.txt").GetDrive(), P_(""));
	VERIFY_EQUAL(P_("\\directory\\file.txt").GetDirectory(), P_("\\directory\\"));
	VERIFY_EQUAL(P_("\\directory\\file.txt").GetDirectoryWithDrive(), P_("\\directory\\"));
	VERIFY_EQUAL(P_("\\directory\\file.txt").GetFilenameBase(), P_("file"));
	VERIFY_EQUAL(P_("\\directory\\file.txt").GetFilenameExtension(), P_(".txt"));
	VERIFY_EQUAL(P_("\\directory\\file.txt").GetFilename(), P_("file.txt"));

	VERIFY_EQUAL(P_(".").GetPrefix(), P_(""));
	VERIFY_EQUAL(P_(".").GetDrive(), P_(""));
	VERIFY_EQUAL(P_(".").GetDirectory(), P_(""));
	VERIFY_EQUAL(P_(".").GetDirectoryWithDrive(), P_(""));
	VERIFY_EQUAL(P_(".").GetFilenameBase(), P_("."));
	VERIFY_EQUAL(P_(".").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_(".").GetFilename(), P_("."));

	VERIFY_EQUAL(P_("..").GetPrefix(), P_(""));
	VERIFY_EQUAL(P_("..").GetDrive(), P_(""));
	VERIFY_EQUAL(P_("..").GetDirectory(), P_(""));
	VERIFY_EQUAL(P_("..").GetDirectoryWithDrive(), P_(""));
	VERIFY_EQUAL(P_("..").GetFilenameBase(), P_(".."));
	VERIFY_EQUAL(P_("..").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_("..").GetFilename(), P_(".."));

	VERIFY_EQUAL(P_("dir\\.").GetPrefix(), P_(""));
	VERIFY_EQUAL(P_("dir\\.").GetDrive(), P_(""));
	VERIFY_EQUAL(P_("dir\\.").GetDirectory(), P_("dir\\"));
	VERIFY_EQUAL(P_("dir\\.").GetDirectoryWithDrive(), P_("dir\\"));
	VERIFY_EQUAL(P_("dir\\.").GetFilenameBase(), P_("."));
	VERIFY_EQUAL(P_("dir\\.").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_("dir\\.").GetFilename(), P_("."));

	VERIFY_EQUAL(P_("dir\\..").GetPrefix(), P_(""));
	VERIFY_EQUAL(P_("dir\\..").GetDrive(), P_(""));
	VERIFY_EQUAL(P_("dir\\..").GetDirectory(), P_("dir\\"));
	VERIFY_EQUAL(P_("dir\\..").GetDirectoryWithDrive(), P_("dir\\"));
	VERIFY_EQUAL(P_("dir\\..").GetFilenameBase(), P_(".."));
	VERIFY_EQUAL(P_("dir\\..").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_("dir\\..").GetFilename(), P_(".."));

	VERIFY_EQUAL(P_(".txt").GetPrefix(), P_(""));
	VERIFY_EQUAL(P_(".txt").GetDrive(), P_(""));
	VERIFY_EQUAL(P_(".txt").GetDirectory(), P_(""));
	VERIFY_EQUAL(P_(".txt").GetDirectoryWithDrive(), P_(""));
	VERIFY_EQUAL(P_(".txt").GetFilenameBase(), P_(".txt"));
	VERIFY_EQUAL(P_(".txt").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_(".txt").GetFilename(), P_(".txt"));

	VERIFY_EQUAL(P_("C:tmp.txt").GetPrefix(), P_(""));
	VERIFY_EQUAL(P_("C:tmp.txt").GetDrive(), P_("C:"));
	VERIFY_EQUAL(P_("C:tmp.txt").GetDirectory(), P_(""));
	VERIFY_EQUAL(P_("C:tmp.txt").GetDirectoryWithDrive(), P_("C:"));
	VERIFY_EQUAL(P_("C:tmp.txt").GetFilenameBase(), P_("tmp"));
	VERIFY_EQUAL(P_("C:tmp.txt").GetFilenameExtension(), P_(".txt"));
	VERIFY_EQUAL(P_("C:tmp.txt").GetFilename(), P_("tmp.txt"));

	VERIFY_EQUAL(P_("C:tempdir\\tmp.txt").GetPrefix(), P_(""));
	VERIFY_EQUAL(P_("C:tempdir\\tmp.txt").GetDrive(), P_("C:"));
	VERIFY_EQUAL(P_("C:tempdir\\tmp.txt").GetDirectory(), P_("tempdir\\"));
	VERIFY_EQUAL(P_("C:tempdir\\tmp.txt").GetDirectoryWithDrive(), P_("C:tempdir\\"));
	VERIFY_EQUAL(P_("C:tempdir\\tmp.txt").GetFilenameBase(), P_("tmp"));
	VERIFY_EQUAL(P_("C:tempdir\\tmp.txt").GetFilenameExtension(), P_(".txt"));
	VERIFY_EQUAL(P_("C:tempdir\\tmp.txt").GetFilename(), P_("tmp.txt"));

	VERIFY_EQUAL(P_("C:\\tempdir\\tmp.txt").GetPrefix(), P_(""));
	VERIFY_EQUAL(P_("C:\\tempdir\\tmp.txt").GetDrive(), P_("C:"));
	VERIFY_EQUAL(P_("C:\\tempdir\\tmp.txt").GetDirectory(), P_("\\tempdir\\"));
	VERIFY_EQUAL(P_("C:\\tempdir\\tmp.txt").GetDirectoryWithDrive(), P_("C:\\tempdir\\"));
	VERIFY_EQUAL(P_("C:\\tempdir\\tmp.txt").GetFilenameBase(), P_("tmp"));
	VERIFY_EQUAL(P_("C:\\tempdir\\tmp.txt").GetFilenameExtension(), P_(".txt"));
	VERIFY_EQUAL(P_("C:\\tempdir\\tmp.txt").GetFilename(), P_("tmp.txt"));

	VERIFY_EQUAL(P_("C:\\tempdir\\tmp.foo.txt").GetFilenameBase(), P_("tmp.foo"));
	VERIFY_EQUAL(P_("C:\\tempdir\\tmp.foo.txt").GetFilenameExtension(), P_(".txt"));

	VERIFY_EQUAL(P_("\\\\server").GetPrefix(), P_(""));
	VERIFY_EQUAL(P_("\\\\server").GetDrive(), P_("\\\\server"));
	VERIFY_EQUAL(P_("\\\\server").GetDirectory(), P_(""));
	VERIFY_EQUAL(P_("\\\\server").GetDirectoryWithDrive(), P_("\\\\server"));
	VERIFY_EQUAL(P_("\\\\server").GetFilenameBase(), P_(""));
	VERIFY_EQUAL(P_("\\\\server").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_("\\\\server").GetFilename(), P_(""));

	VERIFY_EQUAL(P_("\\\\server\\").GetPrefix(), P_(""));
	VERIFY_EQUAL(P_("\\\\server\\").GetDrive(), P_("\\\\server\\"));
	VERIFY_EQUAL(P_("\\\\server\\").GetDirectory(), P_(""));
	VERIFY_EQUAL(P_("\\\\server\\").GetDirectoryWithDrive(), P_("\\\\server\\"));
	VERIFY_EQUAL(P_("\\\\server\\").GetFilenameBase(), P_(""));
	VERIFY_EQUAL(P_("\\\\server\\").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_("\\\\server\\").GetFilename(), P_(""));

	VERIFY_EQUAL(P_("\\\\server\\share").GetPrefix(), P_(""));
	VERIFY_EQUAL(P_("\\\\server\\share").GetDrive(), P_("\\\\server\\share"));
	VERIFY_EQUAL(P_("\\\\server\\share").GetDirectory(), P_(""));
	VERIFY_EQUAL(P_("\\\\server\\share").GetDirectoryWithDrive(), P_("\\\\server\\share"));
	VERIFY_EQUAL(P_("\\\\server\\share").GetFilenameBase(), P_(""));
	VERIFY_EQUAL(P_("\\\\server\\share").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_("\\\\server\\share").GetFilename(), P_(""));

	VERIFY_EQUAL(P_("\\\\server\\share\\").GetPrefix(), P_(""));
	VERIFY_EQUAL(P_("\\\\server\\share\\").GetDrive(), P_("\\\\server\\share"));
	VERIFY_EQUAL(P_("\\\\server\\share\\").GetDirectory(), P_("\\"));
	VERIFY_EQUAL(P_("\\\\server\\share\\").GetDirectoryWithDrive(), P_("\\\\server\\share\\"));
	VERIFY_EQUAL(P_("\\\\server\\share\\").GetFilenameBase(), P_(""));
	VERIFY_EQUAL(P_("\\\\server\\share\\").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_("\\\\server\\share\\").GetFilename(), P_(""));

	VERIFY_EQUAL(P_("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetPrefix(), P_(""));
	VERIFY_EQUAL(P_("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetDrive(), P_("\\\\server\\share"));
	VERIFY_EQUAL(P_("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetDirectory(), P_("\\dir1\\dir2\\"));
	VERIFY_EQUAL(P_("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetDirectoryWithDrive(), P_("\\\\server\\share\\dir1\\dir2\\"));
	VERIFY_EQUAL(P_("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetFilenameBase(), P_("name.foo"));
	VERIFY_EQUAL(P_("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetFilenameExtension(), P_(".ext"));
	VERIFY_EQUAL(P_("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetFilename(), P_("name.foo.ext"));

#if MPT_OS_WINDOWS_WINNT

	VERIFY_EQUAL(P_("\\\\?\\C:\\tempdir\\dir.2\\tmp.foo.txt").GetPrefix(), P_("\\\\?\\"));
	VERIFY_EQUAL(P_("\\\\?\\C:\\tempdir\\dir.2\\tmp.foo.txt").GetDrive(), P_("C:"));
	VERIFY_EQUAL(P_("\\\\?\\C:\\tempdir\\dir.2\\tmp.foo.txt").GetDirectory(), P_("\\tempdir\\dir.2\\"));
	VERIFY_EQUAL(P_("\\\\?\\C:\\tempdir\\dir.2\\tmp.foo.txt").GetDirectoryWithDrive(), P_("C:\\tempdir\\dir.2\\"));
	VERIFY_EQUAL(P_("\\\\?\\C:\\tempdir\\dir.2\\tmp.foo.txt").GetFilenameBase(), P_("tmp.foo"));
	VERIFY_EQUAL(P_("\\\\?\\C:\\tempdir\\dir.2\\tmp.foo.txt").GetFilenameExtension(), P_(".txt"));
	VERIFY_EQUAL(P_("\\\\?\\C:\\tempdir\\dir.2\\tmp.foo.txt").GetFilename(), P_("tmp.foo.txt"));
	
	VERIFY_EQUAL(P_("\\\\?\\UNC\\server\\share\\dir1\\dir2\\name.foo.ext").GetPrefix(), P_("\\\\?\\UNC"));
	VERIFY_EQUAL(P_("\\\\?\\UNC\\server\\share\\dir1\\dir2\\name.foo.ext").GetDrive(), P_("\\\\server\\share"));
	VERIFY_EQUAL(P_("\\\\?\\UNC\\server\\share\\dir1\\dir2\\name.foo.ext").GetDirectory(), P_("\\dir1\\dir2\\"));
	VERIFY_EQUAL(P_("\\\\?\\UNC\\server\\share\\dir1\\dir2\\name.foo.ext").GetDirectoryWithDrive(), P_("\\\\server\\share\\dir1\\dir2\\"));
	VERIFY_EQUAL(P_("\\\\?\\UNC\\server\\share\\dir1\\dir2\\name.foo.ext").GetFilenameBase(), P_("name.foo"));
	VERIFY_EQUAL(P_("\\\\?\\UNC\\server\\share\\dir1\\dir2\\name.foo.ext").GetFilenameExtension(), P_(".ext"));
	VERIFY_EQUAL(P_("\\\\?\\UNC\\server\\share\\dir1\\dir2\\name.foo.ext").GetFilename(), P_("name.foo.ext"));

#endif

#elif !MPT_OS_DJGPP

	VERIFY_EQUAL(P_("").GetDirectory(), P_(""));
	VERIFY_EQUAL(P_("").GetFilenameBase(), P_(""));
	VERIFY_EQUAL(P_("").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_("").GetFilename(), P_(""));

	VERIFY_EQUAL(P_("/").GetDirectory(), P_("/"));
	VERIFY_EQUAL(P_("/").GetFilenameBase(), P_(""));
	VERIFY_EQUAL(P_("/").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_("/").GetFilename(), P_(""));

	VERIFY_EQUAL(P_("/directory/").GetDirectory(), P_("/directory/"));
	VERIFY_EQUAL(P_("/directory/").GetFilenameBase(), P_(""));
	VERIFY_EQUAL(P_("/directory/").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_("/directory/").GetFilename(), P_(""));

	VERIFY_EQUAL(P_("/directory/file.txt").GetDirectory(), P_("/directory/"));
	VERIFY_EQUAL(P_("/directory/file.txt").GetFilenameBase(), P_("file"));
	VERIFY_EQUAL(P_("/directory/file.txt").GetFilenameExtension(), P_(".txt"));
	VERIFY_EQUAL(P_("/directory/file.txt").GetFilename(), P_("file.txt"));

	VERIFY_EQUAL(P_(".").GetDirectory(), P_(""));
	VERIFY_EQUAL(P_(".").GetFilenameBase(), P_("."));
	VERIFY_EQUAL(P_(".").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_(".").GetFilename(), P_("."));

	VERIFY_EQUAL(P_("..").GetDirectory(), P_(""));
	VERIFY_EQUAL(P_("..").GetFilenameBase(), P_(".."));
	VERIFY_EQUAL(P_("..").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_("..").GetFilename(), P_(".."));

	VERIFY_EQUAL(P_("dir/.").GetDirectory(), P_("dir/"));
	VERIFY_EQUAL(P_("dir/.").GetFilenameBase(), P_("."));
	VERIFY_EQUAL(P_("dir/.").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_("dir/.").GetFilename(), P_("."));

	VERIFY_EQUAL(P_("dir/..").GetDirectory(), P_("dir/"));
	VERIFY_EQUAL(P_("dir/..").GetFilenameBase(), P_(".."));
	VERIFY_EQUAL(P_("dir/..").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_("dir/..").GetFilename(), P_(".."));

	VERIFY_EQUAL(P_(".txt").GetDirectory(), P_(""));
	VERIFY_EQUAL(P_(".txt").GetFilenameBase(), P_(".txt"));
	VERIFY_EQUAL(P_(".txt").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_(".txt").GetFilename(), P_(".txt"));

	VERIFY_EQUAL(P_("//server").GetDirectory(), P_("//"));
	VERIFY_EQUAL(P_("//server").GetFilenameBase(), P_("server"));
	VERIFY_EQUAL(P_("//server").GetFilenameExtension(), P_(""));
	VERIFY_EQUAL(P_("//server").GetFilename(), P_("server"));

#endif // MPT_OS
		
#if MPT_OS_WINDOWS
	VERIFY_EQUAL(P_("").Simplify(), P_(""));
	VERIFY_EQUAL(P_(" ").Simplify(), P_(" "));
	VERIFY_EQUAL(P_("foo\\bar").Simplify(), P_("foo\\bar"));
	VERIFY_EQUAL(P_(".\\foo\\bar").Simplify(), P_(".\\foo\\bar"));
	VERIFY_EQUAL(P_(".\\\\foo\\bar").Simplify(), P_(".\\foo\\bar"));
	VERIFY_EQUAL(P_("./\\foo\\bar").Simplify(), P_(".\\foo\\bar"));
	VERIFY_EQUAL(P_("\\foo\\bar").Simplify(), P_("\\foo\\bar"));
	VERIFY_EQUAL(P_("A:\\name_1\\.\\name_2\\..\\name_3\\").Simplify(), P_("A:\\name_1\\name_3"));
	VERIFY_EQUAL(P_("A:\\name_1\\..\\name_2\\./name_3").Simplify(), P_("A:\\name_2\\name_3"));
	VERIFY_EQUAL(P_("A:\\name_1\\.\\name_2\\.\\name_3\\..\\name_4\\..").Simplify(), P_("A:\\name_1\\name_2"));
	VERIFY_EQUAL(P_("A:foo\\\\bar").Simplify(), P_("A:\\foo\\bar"));
	VERIFY_EQUAL(P_("C:\\..").Simplify(), P_("C:\\"));
	VERIFY_EQUAL(P_("C:\\.").Simplify(), P_("C:\\"));
	VERIFY_EQUAL(P_("\\\\foo\\..\\.bar").Simplify(), P_("\\\\.bar"));
	VERIFY_EQUAL(P_("\\\\foo\\..\\..\\bar").Simplify(), P_("\\\\bar"));
#elif !MPT_OS_DJGPP
	VERIFY_EQUAL(P_("/").Simplify(), P_("/"));
	VERIFY_EQUAL(P_("").Simplify(), P_(""));
	VERIFY_EQUAL(P_(" ").Simplify(), P_(" "));
	VERIFY_EQUAL(P_("foo/bar").Simplify(), P_("foo/bar"));
	VERIFY_EQUAL(P_("./foo/bar").Simplify(), P_("./foo/bar"));
	VERIFY_EQUAL(P_(".//foo/bar").Simplify(), P_("./foo/bar"));
	VERIFY_EQUAL(P_(".//foo/bar").Simplify(), P_("./foo/bar"));
	VERIFY_EQUAL(P_("/foo/bar").Simplify(), P_("/foo/bar"));
	VERIFY_EQUAL(P_("//foo/../.bar").Simplify(), P_("/.bar"));
	VERIFY_EQUAL(P_("//foo/../../bar").Simplify(), P_("/bar"));
#endif // MPT_OS

}




#if MPT_OS_EMSCRIPTEN
#define MPT_EMSCRIPTEN_TEST_PATH_CRASH
#endif // MPT_OS_EMSCRIPTEN


#if !defined(MPT_EMSCRIPTEN_TEST_PATH_CRASH)

MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestPathForeignWindowsNT()
{
	{
		using P = mpt::BasicPathString<mpt::PathTraits<std::string, mpt::PathStyleTag<mpt::PathStyle::WindowsNT>>>;

		VERIFY_EQUAL(P::FromNative("").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetDirectoryWithDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("C:\\").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:\\").GetDrive(), P::FromNative("C:"));
		VERIFY_EQUAL(P::FromNative("C:\\").GetDirectory(), P::FromNative("\\"));
		VERIFY_EQUAL(P::FromNative("C:\\").GetDirectoryWithDrive(), P::FromNative("C:\\"));
		VERIFY_EQUAL(P::FromNative("C:\\").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:\\").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:\\").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("\\directory\\").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetDirectory(), P::FromNative("\\directory\\"));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetDirectoryWithDrive(), P::FromNative("\\directory\\"));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetDirectory(), P::FromNative("\\directory\\"));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetDirectoryWithDrive(), P::FromNative("\\directory\\"));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetFilenameBase(), P::FromNative("file"));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetFilenameExtension(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetFilename(), P::FromNative("file.txt"));

		VERIFY_EQUAL(P::FromNative(".").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".").GetDirectoryWithDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".").GetFilenameBase(), P::FromNative("."));
		VERIFY_EQUAL(P::FromNative(".").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".").GetFilename(), P::FromNative("."));

		VERIFY_EQUAL(P::FromNative("..").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("..").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("..").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("..").GetDirectoryWithDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("..").GetFilenameBase(), P::FromNative(".."));
		VERIFY_EQUAL(P::FromNative("..").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("..").GetFilename(), P::FromNative(".."));

		VERIFY_EQUAL(P::FromNative("dir\\.").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetDirectory(), P::FromNative("dir\\"));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetDirectoryWithDrive(), P::FromNative("dir\\"));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetFilenameBase(), P::FromNative("."));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetFilename(), P::FromNative("."));

		VERIFY_EQUAL(P::FromNative("dir\\..").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetDirectory(), P::FromNative("dir\\"));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetDirectoryWithDrive(), P::FromNative("dir\\"));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetFilenameBase(), P::FromNative(".."));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetFilename(), P::FromNative(".."));

		VERIFY_EQUAL(P::FromNative(".txt").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".txt").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".txt").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".txt").GetDirectoryWithDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".txt").GetFilenameBase(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative(".txt").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".txt").GetFilename(), P::FromNative(".txt"));

		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetDrive(), P::FromNative("C:"));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetDirectoryWithDrive(), P::FromNative("C:"));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetFilenameBase(), P::FromNative("tmp"));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetFilenameExtension(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetFilename(), P::FromNative("tmp.txt"));

		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetDrive(), P::FromNative("C:"));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetDirectory(), P::FromNative("tempdir\\"));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetDirectoryWithDrive(), P::FromNative("C:tempdir\\"));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetFilenameBase(), P::FromNative("tmp"));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetFilenameExtension(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetFilename(), P::FromNative("tmp.txt"));

		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetDrive(), P::FromNative("C:"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetDirectory(), P::FromNative("\\tempdir\\"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetDirectoryWithDrive(), P::FromNative("C:\\tempdir\\"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetFilenameBase(), P::FromNative("tmp"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetFilenameExtension(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetFilename(), P::FromNative("tmp.txt"));

		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.foo.txt").GetFilenameBase(), P::FromNative("tmp.foo"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.foo.txt").GetFilenameExtension(), P::FromNative(".txt"));

		VERIFY_EQUAL(P::FromNative("\\\\server").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server").GetDrive(), P::FromNative("\\\\server"));
		VERIFY_EQUAL(P::FromNative("\\\\server").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server").GetDirectoryWithDrive(), P::FromNative("\\\\server"));
		VERIFY_EQUAL(P::FromNative("\\\\server").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("\\\\server\\").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\").GetDrive(), P::FromNative("\\\\server\\"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\").GetDirectoryWithDrive(), P::FromNative("\\\\server\\"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("\\\\server\\share").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share").GetDrive(), P::FromNative("\\\\server\\share"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share").GetDirectoryWithDrive(), P::FromNative("\\\\server\\share"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\").GetDrive(), P::FromNative("\\\\server\\share"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\").GetDirectory(), P::FromNative("\\"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\").GetDirectoryWithDrive(), P::FromNative("\\\\server\\share\\"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetDrive(), P::FromNative("\\\\server\\share"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetDirectory(), P::FromNative("\\dir1\\dir2\\"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetDirectoryWithDrive(), P::FromNative("\\\\server\\share\\dir1\\dir2\\"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetFilenameBase(), P::FromNative("name.foo"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetFilenameExtension(), P::FromNative(".ext"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetFilename(), P::FromNative("name.foo.ext"));

		VERIFY_EQUAL(P::FromNative("\\\\?\\C:\\tempdir\\dir.2\\tmp.foo.txt").GetPrefix(), P::FromNative("\\\\?\\"));
		VERIFY_EQUAL(P::FromNative("\\\\?\\C:\\tempdir\\dir.2\\tmp.foo.txt").GetDrive(), P::FromNative("C:"));
		VERIFY_EQUAL(P::FromNative("\\\\?\\C:\\tempdir\\dir.2\\tmp.foo.txt").GetDirectory(), P::FromNative("\\tempdir\\dir.2\\"));
		VERIFY_EQUAL(P::FromNative("\\\\?\\C:\\tempdir\\dir.2\\tmp.foo.txt").GetDirectoryWithDrive(), P::FromNative("C:\\tempdir\\dir.2\\"));
		VERIFY_EQUAL(P::FromNative("\\\\?\\C:\\tempdir\\dir.2\\tmp.foo.txt").GetFilenameBase(), P::FromNative("tmp.foo"));
		VERIFY_EQUAL(P::FromNative("\\\\?\\C:\\tempdir\\dir.2\\tmp.foo.txt").GetFilenameExtension(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative("\\\\?\\C:\\tempdir\\dir.2\\tmp.foo.txt").GetFilename(), P::FromNative("tmp.foo.txt"));
	
		VERIFY_EQUAL(P::FromNative("\\\\?\\UNC\\server\\share\\dir1\\dir2\\name.foo.ext").GetPrefix(), P::FromNative("\\\\?\\UNC"));
		VERIFY_EQUAL(P::FromNative("\\\\?\\UNC\\server\\share\\dir1\\dir2\\name.foo.ext").GetDrive(), P::FromNative("\\\\server\\share"));
		VERIFY_EQUAL(P::FromNative("\\\\?\\UNC\\server\\share\\dir1\\dir2\\name.foo.ext").GetDirectory(), P::FromNative("\\dir1\\dir2\\"));
		VERIFY_EQUAL(P::FromNative("\\\\?\\UNC\\server\\share\\dir1\\dir2\\name.foo.ext").GetDirectoryWithDrive(), P::FromNative("\\\\server\\share\\dir1\\dir2\\"));
		VERIFY_EQUAL(P::FromNative("\\\\?\\UNC\\server\\share\\dir1\\dir2\\name.foo.ext").GetFilenameBase(), P::FromNative("name.foo"));
		VERIFY_EQUAL(P::FromNative("\\\\?\\UNC\\server\\share\\dir1\\dir2\\name.foo.ext").GetFilenameExtension(), P::FromNative(".ext"));
		VERIFY_EQUAL(P::FromNative("\\\\?\\UNC\\server\\share\\dir1\\dir2\\name.foo.ext").GetFilename(), P::FromNative("name.foo.ext"));

	}
}

MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestPathForeignWindows9x()
{
	{
		using P = mpt::BasicPathString<mpt::PathTraits<std::string, mpt::PathStyleTag<mpt::PathStyle::Windows9x>>>;

		VERIFY_EQUAL(P::FromNative("").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetDirectoryWithDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("C:\\").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:\\").GetDrive(), P::FromNative("C:"));
		VERIFY_EQUAL(P::FromNative("C:\\").GetDirectory(), P::FromNative("\\"));
		VERIFY_EQUAL(P::FromNative("C:\\").GetDirectoryWithDrive(), P::FromNative("C:\\"));
		VERIFY_EQUAL(P::FromNative("C:\\").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:\\").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:\\").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("\\directory\\").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetDirectory(), P::FromNative("\\directory\\"));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetDirectoryWithDrive(), P::FromNative("\\directory\\"));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetDirectory(), P::FromNative("\\directory\\"));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetDirectoryWithDrive(), P::FromNative("\\directory\\"));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetFilenameBase(), P::FromNative("file"));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetFilenameExtension(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetFilename(), P::FromNative("file.txt"));

		VERIFY_EQUAL(P::FromNative(".").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".").GetDirectoryWithDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".").GetFilenameBase(), P::FromNative("."));
		VERIFY_EQUAL(P::FromNative(".").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".").GetFilename(), P::FromNative("."));

		VERIFY_EQUAL(P::FromNative("..").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("..").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("..").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("..").GetDirectoryWithDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("..").GetFilenameBase(), P::FromNative(".."));
		VERIFY_EQUAL(P::FromNative("..").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("..").GetFilename(), P::FromNative(".."));

		VERIFY_EQUAL(P::FromNative("dir\\.").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetDirectory(), P::FromNative("dir\\"));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetDirectoryWithDrive(), P::FromNative("dir\\"));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetFilenameBase(), P::FromNative("."));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetFilename(), P::FromNative("."));

		VERIFY_EQUAL(P::FromNative("dir\\..").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetDirectory(), P::FromNative("dir\\"));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetDirectoryWithDrive(), P::FromNative("dir\\"));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetFilenameBase(), P::FromNative(".."));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetFilename(), P::FromNative(".."));

		VERIFY_EQUAL(P::FromNative(".txt").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".txt").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".txt").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".txt").GetDirectoryWithDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".txt").GetFilenameBase(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative(".txt").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".txt").GetFilename(), P::FromNative(".txt"));

		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetDrive(), P::FromNative("C:"));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetDirectoryWithDrive(), P::FromNative("C:"));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetFilenameBase(), P::FromNative("tmp"));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetFilenameExtension(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetFilename(), P::FromNative("tmp.txt"));

		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetDrive(), P::FromNative("C:"));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetDirectory(), P::FromNative("tempdir\\"));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetDirectoryWithDrive(), P::FromNative("C:tempdir\\"));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetFilenameBase(), P::FromNative("tmp"));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetFilenameExtension(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetFilename(), P::FromNative("tmp.txt"));

		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetDrive(), P::FromNative("C:"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetDirectory(), P::FromNative("\\tempdir\\"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetDirectoryWithDrive(), P::FromNative("C:\\tempdir\\"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetFilenameBase(), P::FromNative("tmp"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetFilenameExtension(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetFilename(), P::FromNative("tmp.txt"));

		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.foo.txt").GetFilenameBase(), P::FromNative("tmp.foo"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.foo.txt").GetFilenameExtension(), P::FromNative(".txt"));

		VERIFY_EQUAL(P::FromNative("\\\\server").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server").GetDrive(), P::FromNative("\\\\server"));
		VERIFY_EQUAL(P::FromNative("\\\\server").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server").GetDirectoryWithDrive(), P::FromNative("\\\\server"));
		VERIFY_EQUAL(P::FromNative("\\\\server").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("\\\\server\\").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\").GetDrive(), P::FromNative("\\\\server\\"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\").GetDirectoryWithDrive(), P::FromNative("\\\\server\\"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("\\\\server\\share").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share").GetDrive(), P::FromNative("\\\\server\\share"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share").GetDirectoryWithDrive(), P::FromNative("\\\\server\\share"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\").GetDrive(), P::FromNative("\\\\server\\share"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\").GetDirectory(), P::FromNative("\\"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\").GetDirectoryWithDrive(), P::FromNative("\\\\server\\share\\"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetDrive(), P::FromNative("\\\\server\\share"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetDirectory(), P::FromNative("\\dir1\\dir2\\"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetDirectoryWithDrive(), P::FromNative("\\\\server\\share\\dir1\\dir2\\"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetFilenameBase(), P::FromNative("name.foo"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetFilenameExtension(), P::FromNative(".ext"));
		VERIFY_EQUAL(P::FromNative("\\\\server\\share\\dir1\\dir2\\name.foo.ext").GetFilename(), P::FromNative("name.foo.ext"));

	}
}

MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestPathForeignDOSDJGPP()
{
	{
		using P = mpt::BasicPathString<mpt::PathTraits<std::string, mpt::PathStyleTag<mpt::PathStyle::DOS_DJGPP>>>;

		VERIFY_EQUAL(P::FromNative("").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetDirectoryWithDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("C:\\").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:\\").GetDrive(), P::FromNative("C:"));
		VERIFY_EQUAL(P::FromNative("C:\\").GetDirectory(), P::FromNative("\\"));
		VERIFY_EQUAL(P::FromNative("C:\\").GetDirectoryWithDrive(), P::FromNative("C:\\"));
		VERIFY_EQUAL(P::FromNative("C:\\").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:\\").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:\\").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("\\directory\\").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetDirectory(), P::FromNative("\\directory\\"));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetDirectoryWithDrive(), P::FromNative("\\directory\\"));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetDirectory(), P::FromNative("\\directory\\"));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetDirectoryWithDrive(), P::FromNative("\\directory\\"));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetFilenameBase(), P::FromNative("file"));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetFilenameExtension(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative("\\directory\\file.txt").GetFilename(), P::FromNative("file.txt"));

		VERIFY_EQUAL(P::FromNative(".").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".").GetDirectoryWithDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".").GetFilenameBase(), P::FromNative("."));
		VERIFY_EQUAL(P::FromNative(".").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".").GetFilename(), P::FromNative("."));

		VERIFY_EQUAL(P::FromNative("..").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("..").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("..").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("..").GetDirectoryWithDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("..").GetFilenameBase(), P::FromNative(".."));
		VERIFY_EQUAL(P::FromNative("..").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("..").GetFilename(), P::FromNative(".."));

		VERIFY_EQUAL(P::FromNative("dir\\.").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetDirectory(), P::FromNative("dir\\"));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetDirectoryWithDrive(), P::FromNative("dir\\"));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetFilenameBase(), P::FromNative("."));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\.").GetFilename(), P::FromNative("."));

		VERIFY_EQUAL(P::FromNative("dir\\..").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetDirectory(), P::FromNative("dir\\"));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetDirectoryWithDrive(), P::FromNative("dir\\"));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetFilenameBase(), P::FromNative(".."));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir\\..").GetFilename(), P::FromNative(".."));

		VERIFY_EQUAL(P::FromNative(".txt").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".txt").GetDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".txt").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".txt").GetDirectoryWithDrive(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".txt").GetFilenameBase(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative(".txt").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".txt").GetFilename(), P::FromNative(".txt"));

		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetDrive(), P::FromNative("C:"));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetDirectoryWithDrive(), P::FromNative("C:"));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetFilenameBase(), P::FromNative("tmp"));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetFilenameExtension(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative("C:tmp.txt").GetFilename(), P::FromNative("tmp.txt"));

		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetDrive(), P::FromNative("C:"));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetDirectory(), P::FromNative("tempdir\\"));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetDirectoryWithDrive(), P::FromNative("C:tempdir\\"));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetFilenameBase(), P::FromNative("tmp"));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetFilenameExtension(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative("C:tempdir\\tmp.txt").GetFilename(), P::FromNative("tmp.txt"));

		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetPrefix(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetDrive(), P::FromNative("C:"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetDirectory(), P::FromNative("\\tempdir\\"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetDirectoryWithDrive(), P::FromNative("C:\\tempdir\\"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetFilenameBase(), P::FromNative("tmp"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetFilenameExtension(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.txt").GetFilename(), P::FromNative("tmp.txt"));

		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.foo.txt").GetFilenameBase(), P::FromNative("tmp.foo"));
		VERIFY_EQUAL(P::FromNative("C:\\tempdir\\tmp.foo.txt").GetFilenameExtension(), P::FromNative(".txt"));

	}
}

MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestPathForeignPOSIX()
{
	{
		using P = mpt::BasicPathString<mpt::PathTraits<std::string, mpt::PathStyleTag<mpt::PathStyle::Posix>>>;

		VERIFY_EQUAL(P::FromNative("").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("/").GetDirectory(), P::FromNative("/"));
		VERIFY_EQUAL(P::FromNative("/").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("/").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("/").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("/directory/").GetDirectory(), P::FromNative("/directory/"));
		VERIFY_EQUAL(P::FromNative("/directory/").GetFilenameBase(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("/directory/").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("/directory/").GetFilename(), P::FromNative(""));

		VERIFY_EQUAL(P::FromNative("/directory/file.txt").GetDirectory(), P::FromNative("/directory/"));
		VERIFY_EQUAL(P::FromNative("/directory/file.txt").GetFilenameBase(), P::FromNative("file"));
		VERIFY_EQUAL(P::FromNative("/directory/file.txt").GetFilenameExtension(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative("/directory/file.txt").GetFilename(), P::FromNative("file.txt"));

		VERIFY_EQUAL(P::FromNative(".").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".").GetFilenameBase(), P::FromNative("."));
		VERIFY_EQUAL(P::FromNative(".").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".").GetFilename(), P::FromNative("."));

		VERIFY_EQUAL(P::FromNative("..").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("..").GetFilenameBase(), P::FromNative(".."));
		VERIFY_EQUAL(P::FromNative("..").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("..").GetFilename(), P::FromNative(".."));

		VERIFY_EQUAL(P::FromNative("dir/.").GetDirectory(), P::FromNative("dir/"));
		VERIFY_EQUAL(P::FromNative("dir/.").GetFilenameBase(), P::FromNative("."));
		VERIFY_EQUAL(P::FromNative("dir/.").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir/.").GetFilename(), P::FromNative("."));

		VERIFY_EQUAL(P::FromNative("dir/..").GetDirectory(), P::FromNative("dir/"));
		VERIFY_EQUAL(P::FromNative("dir/..").GetFilenameBase(), P::FromNative(".."));
		VERIFY_EQUAL(P::FromNative("dir/..").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("dir/..").GetFilename(), P::FromNative(".."));

		VERIFY_EQUAL(P::FromNative(".txt").GetDirectory(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".txt").GetFilenameBase(), P::FromNative(".txt"));
		VERIFY_EQUAL(P::FromNative(".txt").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(".txt").GetFilename(), P::FromNative(".txt"));

		VERIFY_EQUAL(P::FromNative("//server").GetDirectory(), P::FromNative("//"));
		VERIFY_EQUAL(P::FromNative("//server").GetFilenameBase(), P::FromNative("server"));
		VERIFY_EQUAL(P::FromNative("//server").GetFilenameExtension(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative("//server").GetFilename(), P::FromNative("server"));

	}
}

#endif // !MPT_EMSCRIPTEN_TEST_PATH_CRASH


MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestPathForeign()
{

#if !defined(MPT_EMSCRIPTEN_TEST_PATH_CRASH)
	TestPathForeignWindowsNT();
	TestPathForeignWindows9x();
	TestPathForeignDOSDJGPP();
	TestPathForeignPOSIX();
#endif // !MPT_EMSCRIPTEN_TEST_PATH_CRASH

	{
		using P = mpt::BasicPathString<mpt::PathTraits<std::string, mpt::PathStyleTag<mpt::PathStyle::WindowsNT>>>;
		VERIFY_EQUAL(P::FromNative("").Simplify(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(" ").Simplify(), P::FromNative(" "));
		VERIFY_EQUAL(P::FromNative("foo\\bar").Simplify(), P::FromNative("foo\\bar"));
		VERIFY_EQUAL(P::FromNative(".\\foo\\bar").Simplify(), P::FromNative(".\\foo\\bar"));
		VERIFY_EQUAL(P::FromNative(".\\\\foo\\bar").Simplify(), P::FromNative(".\\foo\\bar"));
		VERIFY_EQUAL(P::FromNative("./\\foo\\bar").Simplify(), P::FromNative(".\\foo\\bar"));
		VERIFY_EQUAL(P::FromNative("\\foo\\bar").Simplify(), P::FromNative("\\foo\\bar"));
		VERIFY_EQUAL(P::FromNative("A:\\name_1\\.\\name_2\\..\\name_3\\").Simplify(), P::FromNative("A:\\name_1\\name_3"));
		VERIFY_EQUAL(P::FromNative("A:\\name_1\\..\\name_2\\./name_3").Simplify(), P::FromNative("A:\\name_2\\name_3"));
		VERIFY_EQUAL(P::FromNative("A:\\name_1\\.\\name_2\\.\\name_3\\..\\name_4\\..").Simplify(), P::FromNative("A:\\name_1\\name_2"));
		VERIFY_EQUAL(P::FromNative("A:foo\\\\bar").Simplify(), P::FromNative("A:\\foo\\bar"));
		VERIFY_EQUAL(P::FromNative("C:\\..").Simplify(), P::FromNative("C:\\"));
		VERIFY_EQUAL(P::FromNative("C:\\.").Simplify(), P::FromNative("C:\\"));
		VERIFY_EQUAL(P::FromNative("\\\\foo\\..\\.bar").Simplify(), P::FromNative("\\\\.bar"));
		VERIFY_EQUAL(P::FromNative("\\\\foo\\..\\..\\bar").Simplify(), P::FromNative("\\\\bar"));
	}
	{
		using P = mpt::BasicPathString<mpt::PathTraits<std::string, mpt::PathStyleTag<mpt::PathStyle::Windows9x>>>;
		VERIFY_EQUAL(P::FromNative("").Simplify(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(" ").Simplify(), P::FromNative(" "));
		VERIFY_EQUAL(P::FromNative("foo\\bar").Simplify(), P::FromNative("foo\\bar"));
		VERIFY_EQUAL(P::FromNative(".\\foo\\bar").Simplify(), P::FromNative(".\\foo\\bar"));
		VERIFY_EQUAL(P::FromNative(".\\\\foo\\bar").Simplify(), P::FromNative(".\\foo\\bar"));
		VERIFY_EQUAL(P::FromNative("./\\foo\\bar").Simplify(), P::FromNative(".\\foo\\bar"));
		VERIFY_EQUAL(P::FromNative("\\foo\\bar").Simplify(), P::FromNative("\\foo\\bar"));
		VERIFY_EQUAL(P::FromNative("A:\\name_1\\.\\name_2\\..\\name_3\\").Simplify(), P::FromNative("A:\\name_1\\name_3"));
		VERIFY_EQUAL(P::FromNative("A:\\name_1\\..\\name_2\\./name_3").Simplify(), P::FromNative("A:\\name_2\\name_3"));
		VERIFY_EQUAL(P::FromNative("A:\\name_1\\.\\name_2\\.\\name_3\\..\\name_4\\..").Simplify(), P::FromNative("A:\\name_1\\name_2"));
		VERIFY_EQUAL(P::FromNative("A:foo\\\\bar").Simplify(), P::FromNative("A:\\foo\\bar"));
		VERIFY_EQUAL(P::FromNative("C:\\..").Simplify(), P::FromNative("C:\\"));
		VERIFY_EQUAL(P::FromNative("C:\\.").Simplify(), P::FromNative("C:\\"));
		VERIFY_EQUAL(P::FromNative("\\\\foo\\..\\.bar").Simplify(), P::FromNative("\\\\.bar"));
		VERIFY_EQUAL(P::FromNative("\\\\foo\\..\\..\\bar").Simplify(), P::FromNative("\\\\bar"));
	}
	{
		using P = mpt::BasicPathString<mpt::PathTraits<std::string, mpt::PathStyleTag<mpt::PathStyle::DOS_DJGPP>>>;
		VERIFY_EQUAL(P::FromNative("").Simplify(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(" ").Simplify(), P::FromNative(" "));
		VERIFY_EQUAL(P::FromNative("foo\\bar").Simplify(), P::FromNative("foo\\bar"));
		VERIFY_EQUAL(P::FromNative(".\\foo\\bar").Simplify(), P::FromNative(".\\foo\\bar"));
		VERIFY_EQUAL(P::FromNative(".\\\\foo\\bar").Simplify(), P::FromNative(".\\foo\\bar"));
		VERIFY_EQUAL(P::FromNative("./\\foo\\bar").Simplify(), P::FromNative(".\\foo\\bar"));
		VERIFY_EQUAL(P::FromNative("\\foo\\bar").Simplify(), P::FromNative("\\foo\\bar"));
		VERIFY_EQUAL(P::FromNative("A:\\name_1\\.\\name_2\\..\\name_3\\").Simplify(), P::FromNative("A:\\name_1\\name_3"));
		VERIFY_EQUAL(P::FromNative("A:\\name_1\\..\\name_2\\./name_3").Simplify(), P::FromNative("A:\\name_2\\name_3"));
		VERIFY_EQUAL(P::FromNative("A:\\name_1\\.\\name_2\\.\\name_3\\..\\name_4\\..").Simplify(), P::FromNative("A:\\name_1\\name_2"));
		VERIFY_EQUAL(P::FromNative("A:foo\\\\bar").Simplify(), P::FromNative("A:\\foo\\bar"));
		VERIFY_EQUAL(P::FromNative("C:\\..").Simplify(), P::FromNative("C:\\"));
		VERIFY_EQUAL(P::FromNative("C:\\.").Simplify(), P::FromNative("C:\\"));
	}
	{
		using P = mpt::BasicPathString < mpt::PathTraits<std::string, mpt::PathStyleTag<mpt::PathStyle::Posix>>>;
		VERIFY_EQUAL(P::FromNative("/").Simplify(), P::FromNative("/"));
		VERIFY_EQUAL(P::FromNative("").Simplify(), P::FromNative(""));
		VERIFY_EQUAL(P::FromNative(" ").Simplify(), P::FromNative(" "));
		VERIFY_EQUAL(P::FromNative("foo/bar").Simplify(), P::FromNative("foo/bar"));
		VERIFY_EQUAL(P::FromNative("./foo/bar").Simplify(), P::FromNative("./foo/bar"));
		VERIFY_EQUAL(P::FromNative(".//foo/bar").Simplify(), P::FromNative("./foo/bar"));
		VERIFY_EQUAL(P::FromNative(".//foo/bar").Simplify(), P::FromNative("./foo/bar"));
		VERIFY_EQUAL(P::FromNative("/foo/bar").Simplify(), P::FromNative("/foo/bar"));
		VERIFY_EQUAL(P::FromNative("//foo/../.bar").Simplify(), P::FromNative("/.bar"));
		VERIFY_EQUAL(P::FromNative("//foo/../../bar").Simplify(), P::FromNative("/bar"));
	}

}



MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestCharsets()
{

	// Path conversions
#ifdef MODPLUG_TRACKER
	const mpt::PathString exePath = P_("C:\\OpenMPT\\");
	VERIFY_EQUAL(mpt::AbsolutePathToRelative(P_("C:\\OpenMPT\\"), exePath), P_(".\\"));
	VERIFY_EQUAL(mpt::AbsolutePathToRelative(P_("c:\\OpenMPT\\foo"), exePath), P_(".\\foo"));
	VERIFY_EQUAL(mpt::AbsolutePathToRelative(P_("C:\\foo"), exePath), P_("\\foo"));
	VERIFY_EQUAL(mpt::RelativePathToAbsolute(P_(".\\"), exePath), P_("C:\\OpenMPT\\"));
	VERIFY_EQUAL(mpt::RelativePathToAbsolute(P_(".\\foo"), exePath), P_("C:\\OpenMPT\\foo"));
	VERIFY_EQUAL(mpt::RelativePathToAbsolute(P_("..\\foo"), exePath).Simplify(), P_("C:\\foo"));
	VERIFY_EQUAL(mpt::RelativePathToAbsolute(P_("..\\..\\foo"), exePath).Simplify(), P_("C:\\foo"));
	VERIFY_EQUAL(mpt::RelativePathToAbsolute(P_("file"), exePath), P_("C:\\OpenMPT\\file"));
	VERIFY_EQUAL(mpt::RelativePathToAbsolute(P_("\\foo"), exePath), P_("C:\\foo"));
	VERIFY_EQUAL(mpt::AbsolutePathToRelative(P_("\\\\server\\path\\file"), exePath), P_("\\\\server\\path\\file"));
	VERIFY_EQUAL(mpt::RelativePathToAbsolute(P_("\\\\server\\path\\file"), exePath), P_("\\\\server\\path\\file"));
	VERIFY_EQUAL(mpt::AbsolutePathToRelative(P_("C:\\OpenMPT"), mpt::PathString{}), P_("C:\\OpenMPT"));
	VERIFY_EQUAL(mpt::RelativePathToAbsolute(P_("C:\\OpenMPT"), mpt::PathString{}), P_("C:\\OpenMPT"));
#endif

#ifdef MODPLUG_TRACKER
#if MPT_COMPILER_MSVC

	// Tracker code currently relies on BROKEN MSVC behaviour with respect to %s
	// handling in printf functions when the target is a wide string.
	// Ensure that Microsoft KEEPS this BROKEN behavour (it conflicts with C99 and
	// C++11 demanded semantics.
	// Additionally, the tracker code makes use of %hs and %ls which are the only
	// way to unconditionally expect narrow or wide string parameters
	// respectively; %ls is standard conforming, %hs is a Microsoft extension.
	// We check all possible combinations and expect Microsoft-style behaviour
	// here.

	char src_char[256];
	wchar_t src_wchar_t[256];
	TCHAR src_TCHAR[256];

	char dst_char[256];
	wchar_t dst_wchar_t[256];
	TCHAR dst_TCHAR[256];

	MemsetZero(src_char);
	MemsetZero(src_wchar_t);
	MemsetZero(src_TCHAR);

	strcpy(src_char, "ab");
	wcscpy(src_wchar_t, L"ab");
	_tcscpy(src_TCHAR, _T("ab"));

#define MPT_TEST_PRINTF(dst_type, function, format, src_type) \
	do { \
		MemsetZero(dst_ ## dst_type); \
		function(dst_ ## dst_type, format, src_ ## src_type); \
		VERIFY_EQUAL(std::char_traits< dst_type >::compare(dst_ ## dst_type, src_ ## dst_type, std::char_traits< dst_type >::length( src_ ## dst_type ) + 1), 0); \
	} while(0) \
/**/

#define MPT_TEST_PRINTF_N(dst_type, function, format, src_type) \
	do { \
		MemsetZero(dst_ ## dst_type); \
		function(dst_ ## dst_type, 255, format, src_ ## src_type); \
		VERIFY_EQUAL(std::char_traits< dst_type >::compare(dst_ ## dst_type, src_ ## dst_type, std::char_traits< dst_type >::length( src_ ## dst_type ) + 1), 0); \
	} while(0) \
/**/

	// CRT narrow
	MPT_TEST_PRINTF(char, sprintf, "%s", char);
	MPT_TEST_PRINTF(char, sprintf, "%S", wchar_t);
	MPT_TEST_PRINTF(char, sprintf, "%hs", char);
	MPT_TEST_PRINTF(char, sprintf, "%hS", char);
	MPT_TEST_PRINTF(char, sprintf, "%ls", wchar_t);
	MPT_TEST_PRINTF(char, sprintf, "%lS", wchar_t);
	MPT_TEST_PRINTF(char, sprintf, "%ws", wchar_t);
	MPT_TEST_PRINTF(char, sprintf, "%wS", wchar_t);

	// CRT wide
	MPT_TEST_PRINTF_N(wchar_t, swprintf, L"%s", wchar_t);
	MPT_TEST_PRINTF_N(wchar_t, swprintf, L"%S", char);
	MPT_TEST_PRINTF_N(wchar_t, swprintf, L"%hs", char);
	MPT_TEST_PRINTF_N(wchar_t, swprintf, L"%hS", char);
	MPT_TEST_PRINTF_N(wchar_t, swprintf, L"%ls", wchar_t);
	MPT_TEST_PRINTF_N(wchar_t, swprintf, L"%lS", wchar_t);
	MPT_TEST_PRINTF_N(wchar_t, swprintf, L"%ws", wchar_t);
	MPT_TEST_PRINTF_N(wchar_t, swprintf, L"%wS", wchar_t);

	// WinAPI TCHAR
	MPT_TEST_PRINTF(TCHAR, wsprintf, _T("%s"), TCHAR);
	MPT_TEST_PRINTF(TCHAR, wsprintf, _T("%hs"), char);
	MPT_TEST_PRINTF(TCHAR, wsprintf, _T("%hS"), char);
	MPT_TEST_PRINTF(TCHAR, wsprintf, _T("%ls"), wchar_t);
	MPT_TEST_PRINTF(TCHAR, wsprintf, _T("%lS"), wchar_t);

	// WinAPI CHAR
	MPT_TEST_PRINTF(char, wsprintfA, "%s", char);
	MPT_TEST_PRINTF(char, wsprintfA, "%S", wchar_t);
	MPT_TEST_PRINTF(char, wsprintfA, "%hs", char);
	MPT_TEST_PRINTF(char, wsprintfA, "%hS", char);
	MPT_TEST_PRINTF(char, wsprintfA, "%ls", wchar_t);
	MPT_TEST_PRINTF(char, wsprintfA, "%lS", wchar_t);

	// WinAPI WCHAR
	MPT_TEST_PRINTF(wchar_t, wsprintfW, L"%s", wchar_t);
	MPT_TEST_PRINTF(wchar_t, wsprintfW, L"%S", char);
	MPT_TEST_PRINTF(wchar_t, wsprintfW, L"%hs", char);
	MPT_TEST_PRINTF(wchar_t, wsprintfW, L"%hS", char);
	MPT_TEST_PRINTF(wchar_t, wsprintfW, L"%ls", wchar_t);
	MPT_TEST_PRINTF(wchar_t, wsprintfW, L"%lS", wchar_t);

#undef MPT_TEST_PRINTF
#undef MPT_TEST_PRINTF_n

#endif
#endif

	VERIFY_EQUAL(mpt::CompareNoCaseAscii("f", "f", 6) == 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("f", "F", 6) == 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("F", "f", 6) == 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("f", "g", 6) < 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("h", "g", 6) > 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("fgh", "FgH", 6) == 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("012345678", "012345678", 9) == 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("", "012345678", 9) < 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("FgH", "", 6) > 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("FgH", "F", 6) > 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("FgH", "Fg", 6) > 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("FgH", "fg", 6) > 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("0123456789", "FgH", 0) == 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("FgH", "fgh", 1) == 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("FgH", "fgh", 2) == 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("FgH", "fgh", 3) == 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("FgH", "fghi", 3) == 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("FgH", "fghi", 4) < 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("FIH", "fghi", 1) == 0, true);
	VERIFY_EQUAL(mpt::CompareNoCaseAscii("FIH", "fghi", 2) > 0, true);

}


#ifdef MODPLUG_TRACKER

struct CustomSettingsTestType
{
	float x;
	float y;
	CustomSettingsTestType(float x_ = 0.0f, float y_ = 0.0f) : x(x_), y(y_) { }
};

} // namespace Test

template <>
inline Test::CustomSettingsTestType FromSettingValue(const SettingValue &val)
{
	MPT_ASSERT(val.GetTypeTag() == "myType");
	mpt::ustring xy = val.as<mpt::ustring>();
	if(xy.empty())
	{
		return Test::CustomSettingsTestType(0.0f, 0.0f);
	}
	std::size_t pos = xy.find(U_("|"));
	mpt::ustring x = xy.substr(0, pos);
	mpt::ustring y = xy.substr(pos + 1);
	return Test::CustomSettingsTestType(mpt::parse<float>(x), mpt::parse<float>(y));
}

template <>
inline SettingValue ToSettingValue(const Test::CustomSettingsTestType &val)
{
	return SettingValue(mpt::ufmt::val(val.x) + U_("|") + mpt::ufmt::val(val.y), "myType");
}

namespace Test {

#endif // MODPLUG_TRACKER


#ifdef MODPLUG_TRACKER

template <typename Backend>
static void TestIniSettingsBackendRead(const mpt::PathString &filename)
{

	// test duplicate sections and keys
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream(); 
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::Locale, U_("[S1]")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::Locale, U_("foo=1")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::Locale, U_("foo=2")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::Locale, U_("bar1=a")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::Locale, U_("[S1]")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::Locale, U_("foo=3")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::Locale, U_("foo=4")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::Locale, U_("bar2=a")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("S1"), U_("foo")}, 0).as<int>(), 1);
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("S1"), U_("bar1")}, U_("empty")).as<mpt::ustring>(), U_("a"));
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("S1"), U_("bar2")}, U_("empty")).as<mpt::ustring>(), U_("empty"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// test last value without line ending
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteText(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8("Foo=bar")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo")}, U_("")).as<mpt::ustring>(), U_("bar"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// whitespace trimming
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8(" Foo = bar ")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo")}, U_("")).as<mpt::ustring>(), U_("bar"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// whitespace trimming in section
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, " [Test] ");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8("Foo=bar")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo")}, U_("")).as<mpt::ustring>(), U_("bar"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// whitespace trimming in section
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[ Test ]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8("Foo=bar")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo")}, U_("")).as<mpt::ustring>(), U_("bar"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// junk after section
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test] hurz");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8("Foo=bar")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo")}, U_("")).as<mpt::ustring>(), U_("bar"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// junk section
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test]]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8("Foo=bar")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo")}, U_("")).as<mpt::ustring>(), U_("bar"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// junk section
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8("Foo=bar")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("[Test"), U_("Foo")}, U_("")).as<mpt::ustring>(), U_("bar"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// whitespace trimming in section name
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[ Test ]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8("Foo=bar")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo")}, U_("")).as<mpt::ustring>(), U_("bar"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// commented section (failure)
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[;Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8("Foo=bar")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_(";Test"), U_("Foo")}, U_("")).as<mpt::ustring>(), U_("bar"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// commented key
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8(";Foo=bar")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_(";Foo")}, U_("")).as<mpt::ustring>(), U_(""));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// commented key with whitespace
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8(" ;Foo=bar")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_(";Foo")}, U_("")).as<mpt::ustring>(), U_(""));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// comment on line (failure)
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8("Foo=bar ; baz")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo")}, U_("")).as<mpt::ustring>(), U_("bar ; baz"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// quoted strings
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8(" Foo = \"bar\" ")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo")}, U_("")).as<mpt::ustring>(), U_("bar"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// quoted strings
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8(" Foo = \"bar \" ")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo")}, U_("")).as<mpt::ustring>(), U_("bar "));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// misquoted strings
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8("Foo=\"")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo")}, U_("")).as<mpt::ustring>(), U_("\""));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// misquoted strings
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8(" Foo = \"bar")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo")}, U_("")).as<mpt::ustring>(), U_("\"bar"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// misquoted strings
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8(" Foo = \"ba\"r\" ")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo")}, U_("")).as<mpt::ustring>(), U_("ba\"r"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// misquoted strings
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8(" Foo = \"ba\" r ")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo")}, U_("")).as<mpt::ustring>(), U_("\"ba\" r"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// misquoted strings
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8("Foo1='ba' r ")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8("Foo2='ba''")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8("Foo3=\"''\"")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo1")}, U_("")).as<mpt::ustring>(), U_("'ba' r"));
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo2")}, U_("")).as<mpt::ustring>(), U_("ba'"));
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo3")}, U_("")).as<mpt::ustring>(), U_("''"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// quoted integer
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8(" Foo = \"1\" ")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo")}, 9).as<int32>(), 1);
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// quoted section and key (failure)
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, " [ \"Test\" ] ");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8(" \"Foo\" = bar ")));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("\"Test\""), U_("\"Foo\"")}, U_("")).as<mpt::ustring>(), U_("bar"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// Unicode in UTF-16LE-BOM mode
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			auto WriteUTF16LE = [](auto & outputstream, mpt::span<const WCHAR> data) {
					mpt::IO::WriteRaw(outputstream, reinterpret_cast<const std::byte*>(data.data()), data.size() * 2);
				};
			std::vector<WCHAR> utf16lebom = {0xfeff};
			WriteUTF16LE(outputstream, mpt::as_span(utf16lebom));
			WriteUTF16LE(outputstream, mpt::as_span(std::wstring(L"[Test]\r\n")));
			WriteUTF16LE(outputstream, mpt::as_span(mpt::ToWide(MPT_UTF8("Umlauts=\xC3\xA4\r\n"))));
		}
		{
			Backend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Umlauts")}, U_("")).as<mpt::ustring>(), MPT_UTF8("\xC3\xA4"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

}

template <typename Backend>
static void TestIniSettingsBackendReadCaseInsensitive(const mpt::PathString &filename)
{
	// case
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, U_("Foo=a")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, U_("foo=b")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, U_("bar=a")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, U_("Bar=b")));
		}
		{
			Backend inifile{filename};
			if(inifile.GetCaseSensitivity() == CaseSensitivity::Insensitive)
			{
				VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo")}, U_("")).as<mpt::ustring>(), U_("a"));
				VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Bar")}, U_("")).as<mpt::ustring>(), U_("a"));
			}
		}
		{
			Backend inifile{filename};
			if(inifile.GetCaseSensitivity() == CaseSensitivity::Insensitive)
			{
				VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("foo")}, U_("")).as<mpt::ustring>(), U_("a"));
				VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("bar")}, U_("")).as<mpt::ustring>(), U_("a"));
			}
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}
}

#endif // MODPLUG_TRACKER


MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestSettings()
{

#ifdef MODPLUG_TRACKER

	VERIFY_EQUAL(SettingPath(U_("a"),U_("b")) < SettingPath(U_("a"),U_("c")), true);
	VERIFY_EQUAL(!(SettingPath(U_("c"),U_("b")) < SettingPath(U_("a"),U_("c"))), true);

	const mpt::PathString filename = theApp.GetConfigPath() + P_("test.ini");

	SetFileAttributes(mpt::support_long_path(filename.AsNative()).c_str(), (~FILE_ATTRIBUTE_READONLY) & GetFileAttributes(mpt::support_long_path(filename.AsNative()).c_str()));
	DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());

	TestIniSettingsBackendRead<ImmediateWindowsIniFileSettingsBackend>(filename);
	TestIniSettingsBackendRead<BatchedWindowsIniFileSettingsBackend>(filename);
	TestIniSettingsBackendRead<CachedIniFileSettingsBackend>(filename);

	TestIniSettingsBackendReadCaseInsensitive<ImmediateWindowsIniFileSettingsBackend>(filename);
	TestIniSettingsBackendReadCaseInsensitive<BatchedWindowsIniFileSettingsBackend>(filename);
	TestIniSettingsBackendReadCaseInsensitive<CachedIniFileSettingsBackend>(filename);

	{
		IniFileSettingsContainer conf{filename};
		int32 foobar = conf.Read(U_("Test"), U_("bar"), 23);
		conf.Write(U_("Test"), U_("bar"), 64);
		conf.Write(U_("Test"), U_("bar"), 42);
		conf.Read(U_("Test"), U_("baz"), 4711);
		foobar = conf.Read(U_("Test"), U_("bar"), 28);
		VERIFY_EQUAL(foobar, 42);
	}

	{
		IniFileSettingsContainer conf{filename};
		int32 foobar = conf.Read(U_("Test"), U_("bar"), 28);
		VERIFY_EQUAL(foobar, 42);
		conf.Write(U_("Test"), U_("bar"), 43);
	}

	{
		IniFileSettingsContainer conf{filename};
		int32 foobar = conf.Read(U_("Test"), U_("bar"), 123);
		VERIFY_EQUAL(foobar, 43);
		conf.Write(U_("Test"), U_("bar"), 88);
	}

	{
		IniFileSettingsContainer conf{filename};
		Setting<int> foo(conf, U_("Test"), U_("bar"), 99);
		VERIFY_EQUAL(foo, 88);
		foo = 7;
	}

	{
		IniFileSettingsContainer conf{filename};
		Setting<int> foo(conf, U_("Test"), U_("bar"), 99);
		VERIFY_EQUAL(foo, 7);
	}

	{
		IniFileSettingsContainer conf{filename};
		conf.Read(U_("Test"), U_("struct"), std::string(""));
		conf.Write(U_("Test"), U_("struct"), std::string(""));
	}

	{
		IniFileSettingsContainer conf{filename};
		CustomSettingsTestType dummy = conf.Read(U_("Test"), U_("struct"), CustomSettingsTestType(1.0f, 1.0f));
		dummy = CustomSettingsTestType(0.125f, 32.0f);
		conf.Write(U_("Test"), U_("struct"), dummy);
	}

	{
		IniFileSettingsContainer conf{filename};
		Setting<CustomSettingsTestType> dummyVar(conf, U_("Test"), U_("struct"), CustomSettingsTestType(1.0f, 1.0f));
		CustomSettingsTestType dummy = dummyVar;
		VERIFY_EQUAL(dummy.x, 0.125f);
		VERIFY_EQUAL(dummy.y, 32.0f);
	}

#if 0
	// test Unicode in UTF-8-BOM mode, Wine-only
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			std::vector<std::byte> utf8bom = {mpt::byte_cast<std::byte>(static_cast<uint8>(0xEF)), mpt::byte_cast<std::byte>(static_cast<uint8>(0xBB)), mpt::byte_cast<std::byte>(static_cast<uint8>(0xBF))};
			mpt::IO::WriteRaw(outputstream, mpt::as_span(utf8bom));
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, MPT_UTF8("Umlauts=\xC3\xA4")));
		}
		{
			IniFileSettingsBackend inifile{filename};
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Umlauts")}, U_("")).as<mpt::ustring>(), MPT_UTF8("\xC3\xA4"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}
#endif

	// whitespace and control characters
	mpt::ustring spacespacecharspacespace = MPT_USTRING("  X  ");
	mpt::ustring threespaces = MPT_USTRING("   ");
	mpt::ustring tab = MPT_USTRING("\t");
	mpt::ustring tokens = MPT_USTRING("[]=;");
	mpt::ustring xcrlfy = MPT_USTRING("x\r\ny");
	mpt::ustring cc0;
	for(mpt::uchar c = 0; c < 0x20; ++c)
	{
		cc0.push_back(c);
	}

	{
		{
			FileSettingsContainer<ImmediateWindowsIniFileSettingsBackend> conf{filename};
			conf.Write<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("spacespacecharspacespace")), spacespacecharspacespace);
			conf.Write<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("threespaces")), threespaces);
			conf.Write<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("tab")), tab);
			conf.Write<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("tokens")), tokens);
			conf.Write<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("xcrlfy")), xcrlfy);
			conf.Write<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("cc0")), cc0);
		}
		{
			FileSettingsContainer<ImmediateWindowsIniFileSettingsBackend> conf{filename};
			//VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("spacespacecharspacespace"))), spacespacecharspacespace);
			//VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("threespaces"))), threespaces);
			//VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("tab"))), tab);
			//VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("tokens"))), tokens);
			//VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("xcrlfy"))), xcrlfy);
			//VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("cc0"))), cc0);
			VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("spacespacecharspacespace"))), mpt::trim(spacespacecharspacespace));
			VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("threespaces"))), mpt::trim(threespaces));
			VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("tab"))), mpt::trim(tab));
			VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("tokens"))), tokens);
			//VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("xcrlfy"))), xcrlfy);
			//VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("cc0"))), U_(""));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	{
		{
			FileSettingsContainer<CachedIniFileSettingsBackend> conf{filename};
			conf.Write<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("spacespacecharspacespace")), spacespacecharspacespace);
			conf.Write<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("threespaces")), threespaces);
			conf.Write<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("tab")), tab);
			conf.Write<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("tokens")), tokens);
			conf.Write<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("xcrlfy")), xcrlfy);
			conf.Write<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("cc0")), cc0);
		}
		{
			FileSettingsContainer<CachedIniFileSettingsBackend> conf{filename};
			VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("spacespacecharspacespace"))), spacespacecharspacespace);
			VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("threespaces"))), threespaces);
			VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("tab"))), tab);
			VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("tokens"))), tokens);
			VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("xcrlfy"))), xcrlfy);
			VERIFY_EQUAL(conf.Read<mpt::ustring>(SettingPath(MPT_USTRING("Test"), MPT_USTRING("cc0"))), cc0);
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// escaping
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, U_("Foo1 = ^")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, U_("Foo2 = ^^")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, U_("Foo3 = ^^^")));
		}
		{
			FileSettingsContainer<ImmediateWindowsIniFileSettingsBackend> inifile{filename};
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo1")}, U_("")), U_("^"));
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo2")}, U_("")), U_("^^"));
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo3")}, U_("")), U_("^^^"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, U_("Foo1 = ^")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, U_("Foo2 = ^^")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, U_("Foo3 = ^^^")));
		}
		{
			FileSettingsContainer<CachedIniFileSettingsBackend> inifile{filename};
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo1")}, U_("")), U_("^"));
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo2")}, U_("")), U_("^^"));
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo3")}, U_("")), U_("^^^"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}
	{
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
		{
			mpt::IO::SafeOutputFile outputfile{filename, std::ios::binary};
			mpt::IO::ofstream & outputstream = outputfile.stream();
			mpt::IO::WriteTextCRLF(outputstream, "[!Type]");
			mpt::IO::WriteTextCRLF(outputstream, "!Format=org.openmpt.fileformat.ini");
			mpt::IO::WriteTextCRLF(outputstream, "!VersionMajor=4");
			mpt::IO::WriteTextCRLF(outputstream, "!VersionMinor=0");
			mpt::IO::WriteTextCRLF(outputstream, "!VersionPatch=0");
			mpt::IO::WriteTextCRLF(outputstream, "[Test]");
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, U_("Foo1 = ^")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, U_("Foo2 = ^^")));
			mpt::IO::WriteTextCRLF(outputstream, mpt::ToCharset(mpt::Charset::UTF8, U_("Foo3 = ^^^")));
		}
		{
			FileSettingsContainer<CachedIniFileSettingsBackend> inifile{filename};
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo1")}, U_("")), U_("^"));
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo2")}, U_("")), U_("^"));
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo3")}, U_("")), U_("^^"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// case sensitivity
	{
		{	
			FileSettingsContainer<ImmediateWindowsIniFileSettingsBackend> inifile{filename};
			inifile.Write<mpt::ustring>(SettingPath{U_("Test"), U_("foo")}, U_("a"));
		}
		{	
			FileSettingsContainer<ImmediateWindowsIniFileSettingsBackend> inifile{filename};
			inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo")}, U_("b"));
			inifile.Write<mpt::ustring>(SettingPath{U_("Test"), U_("Foo")}, U_("c"));
		}
		{	
#if MPT_SETTINGS_INI_CASE_INSENSITIVE
			CachedIniFileSettingsBackend inifile{filename, CaseSensitivity::Sensitive};
#else
			CachedIniFileSettingsBackend inifile{filename};
#endif
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("Foo")}, SettingValue{U_("")}).as<mpt::ustring>(), U_(""));
			VERIFY_EQUAL(inifile.ReadSetting(SettingPath{U_("Test"), U_("foo")}, SettingValue{U_("")}).as<mpt::ustring>(), U_("c"));
		}
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

	// read-only file
	{
		{	
			FileSettingsContainer<ImmediateWindowsIniFileSettingsBackend> inifile{filename};
			inifile.Write<mpt::ustring>(SettingPath{U_("Test"), U_("Foo")}, U_("a"));
		}
		SetFileAttributes(mpt::support_long_path(filename.AsNative()).c_str(), FILE_ATTRIBUTE_READONLY | GetFileAttributes(mpt::support_long_path(filename.AsNative()).c_str()));
		{	
			FileSettingsContainer<ImmediateWindowsIniFileSettingsBackend> inifile{filename};
			inifile.Write<mpt::ustring>(SettingPath{U_("Test"), U_("Foo")}, U_("b"));
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo")}, U_("c")), U_("b"));
			inifile.Forget(SettingPath{U_("Test"), U_("Foo")});
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo")}, U_("c")), U_("a"));
		}
		{	
			FileSettingsContainer<ImmediateWindowsIniFileSettingsBackend> inifile{filename};
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo")}, U_("c")), U_("a"));
		}
		SetFileAttributes(mpt::support_long_path(filename.AsNative()).c_str(), (~FILE_ATTRIBUTE_READONLY) & GetFileAttributes(mpt::support_long_path(filename.AsNative()).c_str()));
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}
	{
		{	
			FileSettingsContainer<BatchedWindowsIniFileSettingsBackend> inifile{filename};
			inifile.Write<mpt::ustring>(SettingPath{U_("Test"), U_("Foo")}, U_("a"));
		}
		SetFileAttributes(mpt::support_long_path(filename.AsNative()).c_str(), FILE_ATTRIBUTE_READONLY | GetFileAttributes(mpt::support_long_path(filename.AsNative()).c_str()));
		{	
			FileSettingsContainer<BatchedWindowsIniFileSettingsBackend> inifile{filename};
			inifile.Write<mpt::ustring>(SettingPath{U_("Test"), U_("Foo")}, U_("b"));
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo")}, U_("c")), U_("b"));
			inifile.Forget(SettingPath{U_("Test"), U_("Foo")});
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo")}, U_("c")), U_("a"));
		}
		{	
			FileSettingsContainer<BatchedWindowsIniFileSettingsBackend> inifile{filename};
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo")}, U_("c")), U_("a"));
		}
		SetFileAttributes(mpt::support_long_path(filename.AsNative()).c_str(), (~FILE_ATTRIBUTE_READONLY) & GetFileAttributes(mpt::support_long_path(filename.AsNative()).c_str()));
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}
	{
		{	
			FileSettingsContainer<CachedIniFileSettingsBackend> inifile{filename};
			inifile.Write<mpt::ustring>(SettingPath{U_("Test"), U_("Foo")}, U_("a"));
		}
		SetFileAttributes(mpt::support_long_path(filename.AsNative()).c_str(), FILE_ATTRIBUTE_READONLY | GetFileAttributes(mpt::support_long_path(filename.AsNative()).c_str()));
		{	
			FileSettingsContainer<CachedIniFileSettingsBackend> inifile{filename};
			inifile.Write<mpt::ustring>(SettingPath{U_("Test"), U_("Foo")}, U_("b"));
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo")}, U_("c")), U_("b"));
			inifile.Forget(SettingPath{U_("Test"), U_("Foo")});
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo")}, U_("c")), U_("a"));
		}
		{	
			FileSettingsContainer<CachedIniFileSettingsBackend> inifile{filename};
			VERIFY_EQUAL(inifile.Read<mpt::ustring>(SettingPath{U_("Test"), U_("Foo")}, U_("c")), U_("a"));
		}
		SetFileAttributes(mpt::support_long_path(filename.AsNative()).c_str(), (~FILE_ATTRIBUTE_READONLY) & GetFileAttributes(mpt::support_long_path(filename.AsNative()).c_str()));
		DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
	}

#endif // MODPLUG_TRACKER

}


// Test MIDI Event generating / reading
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestMIDIEvents()
{
	uint32 midiEvent;

	midiEvent = MIDIEvents::CC(MIDIEvents::MIDICC_Balance_Coarse, 13, 40);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(midiEvent), MIDIEvents::evControllerChange);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(midiEvent), 13);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(midiEvent), MIDIEvents::MIDICC_Balance_Coarse);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(midiEvent), 40);

	midiEvent = MIDIEvents::NoteOn(10, 50, 120);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(midiEvent), MIDIEvents::evNoteOn);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(midiEvent), 10);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(midiEvent), 50);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(midiEvent), 120);

	midiEvent = MIDIEvents::NoteOff(15, 127, 42);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(midiEvent), MIDIEvents::evNoteOff);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(midiEvent), 15);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(midiEvent), 127);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(midiEvent), 42);

	midiEvent = MIDIEvents::ProgramChange(1, 127);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(midiEvent), MIDIEvents::evProgramChange);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(midiEvent), 1);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(midiEvent), 127);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(midiEvent), 0);

	midiEvent = MIDIEvents::PitchBend(2, MIDIEvents::pitchBendMax);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(midiEvent), MIDIEvents::evPitchBend);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(midiEvent), 2);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(midiEvent), 0x7F);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(midiEvent), 0x7F);

	midiEvent = MIDIEvents::System(MIDIEvents::sysStart);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(midiEvent), MIDIEvents::evSystem);
	VERIFY_EQUAL_NONCONT(midiEvent & 0xFF, MIDIEvents::sysStart);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(midiEvent), MIDIEvents::sysStart & 0x0F);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(midiEvent), 0);
	VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(midiEvent), 0);
}


// Check if our test file was loaded correctly.
static void TestLoadXMFile(const CSoundFile &sndFile)
{
#ifdef MODPLUG_TRACKER
	const CModDoc *pModDoc = sndFile.GetpModDoc();
	VERIFY_EQUAL_NONCONT(pModDoc->IsChannelUnused(0), true);
	VERIFY_EQUAL_NONCONT(pModDoc->IsChannelUnused(1), false);
#endif // MODPLUG_TRACKER

	// Global Variables
	VERIFY_EQUAL_NONCONT(sndFile.GetTitle(), "Test Module");
	VERIFY_EQUAL_NONCONT(sndFile.m_songMessage.substr(0, 32), "OpenMPT Module Loader Test Suite");
	VERIFY_EQUAL_NONCONT(sndFile.Order().GetDefaultTempo(), TEMPO(139, 0));
	VERIFY_EQUAL_NONCONT(sndFile.Order().GetDefaultSpeed(), 5);
	VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultGlobalVolume, 128);
	VERIFY_EQUAL_NONCONT(sndFile.m_nVSTiVolume, 42);
	VERIFY_EQUAL_NONCONT(sndFile.m_nSamplePreAmp, 23);
	VERIFY_EQUAL_NONCONT(sndFile.m_SongFlags, SONG_LINEARSLIDES | SONG_EXFILTERRANGE);
	VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[MSF_COMPATIBLE_PLAY], true);
	VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[kMIDICCBugEmulation], false);
	VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[kMPTOldSwingBehaviour], false);
	VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[kOldMIDIPitchBends], false);
	VERIFY_EQUAL_NONCONT(sndFile.GetMixLevels(), MixLevels::Compatible);
	VERIFY_EQUAL_NONCONT(sndFile.m_nTempoMode, TempoMode::Modern);
	VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultRowsPerBeat, 6);
	VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultRowsPerMeasure, 12);
	VERIFY_EQUAL_NONCONT(sndFile.m_dwCreatedWithVersion, MPT_V("1.19.02.05"));
	VERIFY_EQUAL_NONCONT(sndFile.Order().GetRestartPos(), 1);

	// Macros
	VERIFY_EQUAL_NONCONT(sndFile.m_MidiCfg.GetParameteredMacroType(0), kSFxReso);
	VERIFY_EQUAL_NONCONT(sndFile.m_MidiCfg.GetParameteredMacroType(1), kSFxDryWet);
	VERIFY_EQUAL_NONCONT(sndFile.m_MidiCfg.GetFixedMacroType(), kZxxResoFltMode);

	// Channels
	VERIFY_EQUAL_NONCONT(sndFile.GetNumChannels(), 2);
	VERIFY_EQUAL_NONCONT((sndFile.ChnSettings[0].szName == "First Channel"), true);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[0].nMixPlugin, 0);

	VERIFY_EQUAL_NONCONT((sndFile.ChnSettings[1].szName == "Second Channel"), true);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[1].nMixPlugin, 1);

	// Samples
	VERIFY_EQUAL_NONCONT(sndFile.GetNumSamples(), 3);
	VERIFY_EQUAL_NONCONT((sndFile.m_szNames[1] == "Pulse Sample"), true);
	VERIFY_EQUAL_NONCONT((sndFile.m_szNames[2] == "Empty Sample"), true);
	VERIFY_EQUAL_NONCONT((sndFile.m_szNames[3] == "Unassigned Sample"), true);
#ifdef MODPLUG_TRACKER
	VERIFY_EQUAL_NONCONT(pModDoc->FindSampleParent(1), 1);
	VERIFY_EQUAL_NONCONT(pModDoc->FindSampleParent(2), 1);
	VERIFY_EQUAL_NONCONT(pModDoc->FindSampleParent(3), INSTRUMENTINDEX_INVALID);
#endif // MODPLUG_TRACKER
	const ModSample &sample = sndFile.GetSample(1);
	VERIFY_EQUAL_NONCONT(sample.GetBytesPerSample(), 1);
	VERIFY_EQUAL_NONCONT(sample.GetNumChannels(), 1);
	VERIFY_EQUAL_NONCONT(sample.GetElementarySampleSize(), 1);
	VERIFY_EQUAL_NONCONT(sample.GetSampleSizeInBytes(), 16);
	VERIFY_EQUAL_NONCONT(sample.nFineTune, 35);
	VERIFY_EQUAL_NONCONT(sample.RelativeTone, 1);
	VERIFY_EQUAL_NONCONT(sample.nVolume, 32 * 4);
	VERIFY_EQUAL_NONCONT(sample.nGlobalVol, 64);
	VERIFY_EQUAL_NONCONT(sample.nPan, 160);
	VERIFY_EQUAL_NONCONT(sample.uFlags, CHN_PANNING | CHN_LOOP | CHN_PINGPONGLOOP);

	VERIFY_EQUAL_NONCONT(sample.nLoopStart, 1);
	VERIFY_EQUAL_NONCONT(sample.nLoopEnd, 8);

	VERIFY_EQUAL_NONCONT(sample.nVibType, VIB_SQUARE);
	VERIFY_EQUAL_NONCONT(sample.nVibSweep, 3);
	VERIFY_EQUAL_NONCONT(sample.nVibRate, 4);
	VERIFY_EQUAL_NONCONT(sample.nVibDepth, 5);

	// Sample Data
	for(size_t i = 0; i < 6; i++)
	{
		VERIFY_EQUAL_NONCONT(sample.sample8()[i], 18);
	}
	for(size_t i = 6; i < 16; i++)
	{
		VERIFY_EQUAL_NONCONT(sample.sample8()[i], 0);
	}

	// Instruments
	VERIFY_EQUAL_NONCONT(sndFile.GetNumInstruments(), 1);
	const ModInstrument *pIns = sndFile.Instruments[1];
	VERIFY_EQUAL_NONCONT(pIns->nFadeOut, 1024);
	VERIFY_EQUAL_NONCONT(pIns->nPan, 128);
	VERIFY_EQUAL_NONCONT(pIns->dwFlags, InstrumentFlags(0));

	VERIFY_EQUAL_NONCONT(pIns->nPPS, 0);
	VERIFY_EQUAL_NONCONT(pIns->nPPC, NOTE_MIDDLEC - 1);

	VERIFY_EQUAL_NONCONT(pIns->nVolRampUp, 1200);
	VERIFY_EQUAL_NONCONT(pIns->resampling, SRCMODE_SINC8LP);

	VERIFY_EQUAL_NONCONT(pIns->IsCutoffEnabled(), false);
	VERIFY_EQUAL_NONCONT(pIns->GetCutoff(), 0);
	VERIFY_EQUAL_NONCONT(pIns->IsResonanceEnabled(), false);
	VERIFY_EQUAL_NONCONT(pIns->GetResonance(), 0);
	VERIFY_EQUAL_NONCONT(pIns->filterMode, FilterMode::Unchanged);

	VERIFY_EQUAL_NONCONT(pIns->nVolSwing, 0);
	VERIFY_EQUAL_NONCONT(pIns->nPanSwing, 0);
	VERIFY_EQUAL_NONCONT(pIns->nCutSwing, 0);
	VERIFY_EQUAL_NONCONT(pIns->nResSwing, 0);

	VERIFY_EQUAL_NONCONT(pIns->nNNA, NewNoteAction::NoteCut);
	VERIFY_EQUAL_NONCONT(pIns->nDCT, DuplicateCheckType::None);

	VERIFY_EQUAL_NONCONT(pIns->nMixPlug, 1);
	VERIFY_EQUAL_NONCONT(pIns->nMidiChannel, 16);
	VERIFY_EQUAL_NONCONT(pIns->nMidiProgram, 64);
	VERIFY_EQUAL_NONCONT(pIns->wMidiBank, 2);
	VERIFY_EQUAL_NONCONT(pIns->midiPWD, 8);

	VERIFY_EQUAL_NONCONT(pIns->pTuning, nullptr);

	VERIFY_EQUAL_NONCONT(pIns->pitchToTempoLock, TEMPO(0, 0));

	VERIFY_EQUAL_NONCONT(pIns->pluginVelocityHandling, PLUGIN_VELOCITYHANDLING_VOLUME);
	VERIFY_EQUAL_NONCONT(pIns->pluginVolumeHandling, PLUGIN_VOLUMEHANDLING_MIDI);

	for(size_t i = sndFile.GetModSpecifications().noteMin; i < sndFile.GetModSpecifications().noteMax; i++)
	{
		VERIFY_EQUAL_NONCONT(pIns->Keyboard[i], (i == NOTE_MIDDLEC - 1) ? 2 : 1);
	}

	VERIFY_EQUAL_NONCONT(pIns->VolEnv.dwFlags, ENV_ENABLED | ENV_SUSTAIN);
	VERIFY_EQUAL_NONCONT(pIns->VolEnv.size(), 3);
	VERIFY_EQUAL_NONCONT(pIns->VolEnv.nReleaseNode, ENV_RELEASE_NODE_UNSET);
	VERIFY_EQUAL_NONCONT(pIns->VolEnv[2].tick, 96);
	VERIFY_EQUAL_NONCONT(pIns->VolEnv[2].value, 0);
	VERIFY_EQUAL_NONCONT(pIns->VolEnv.nSustainStart, 1);
	VERIFY_EQUAL_NONCONT(pIns->VolEnv.nSustainEnd, 1);

	VERIFY_EQUAL_NONCONT(pIns->PanEnv.dwFlags, ENV_LOOP);
	VERIFY_EQUAL_NONCONT(pIns->PanEnv.size(), 12);
	VERIFY_EQUAL_NONCONT(pIns->PanEnv.nLoopStart, 9);
	VERIFY_EQUAL_NONCONT(pIns->PanEnv.nLoopEnd, 11);
	VERIFY_EQUAL_NONCONT(pIns->PanEnv.nReleaseNode, ENV_RELEASE_NODE_UNSET);
	VERIFY_EQUAL_NONCONT(pIns->PanEnv[9].tick, 46);
	VERIFY_EQUAL_NONCONT(pIns->PanEnv[9].value, 23);

	VERIFY_EQUAL_NONCONT(pIns->PitchEnv.dwFlags, EnvelopeFlags(0));
	VERIFY_EQUAL_NONCONT(pIns->PitchEnv.size(), 0);

	// Sequences
	VERIFY_EQUAL_NONCONT(sndFile.Order.GetNumSequences(), 1);
	VERIFY_EQUAL_NONCONT(sndFile.Order()[0], 0);
	VERIFY_EQUAL_NONCONT(sndFile.Order()[1], 1);

	// Patterns
	VERIFY_EQUAL_NONCONT(sndFile.Patterns.GetNumPatterns(), 2);

	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetName(), "First Pattern");
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetNumRows(), 64);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetNumChannels(), 2);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetOverrideSignature(), false);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetRowsPerBeat(), 0);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetRowsPerMeasure(), 0);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns.IsPatternEmpty(0), true);

	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetName(), "Second Pattern");
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetNumRows(), 32);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetNumChannels(), 2);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetOverrideSignature(), false);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetRowsPerBeat(), 0);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetRowsPerMeasure(), 0);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns.IsPatternEmpty(1), false);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 0)->IsPcNote(), false);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 0)->note, NOTE_NONE);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 0)->instr, 0);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 0)->volcmd, VOLCMD_VIBRATOSPEED);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 0)->vol, 15);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 1)->note, NOTE_MIN + 12);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(1, 1)->note, NOTE_MIN + 12 + 95);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(2, 1)->note, NOTE_KEYOFF);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(30, 0)->IsEmpty(), true);  // Test for resaved out-of-range note
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 0)->IsEmpty(), true);  // Test for resaved out-of-range note
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->IsEmpty(), false);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->IsPcNote(), false);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->note, NOTE_MIDDLEC + 12);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->instr, 45);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->volcmd, VOLCMD_VOLSLIDEDOWN);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->vol, 5);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->command, CMD_PANNING8);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->param, 0xFF);

	// Test 4-Bit Panning conversion
	for(int i = 0; i < 16; i++)
	{
		VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(10 + i, 0)->vol, i * 4);
	}

	// Plugins
	const SNDMIXPLUGIN &plug = sndFile.m_MixPlugins[0];
	VERIFY_EQUAL_NONCONT(plug.GetName(), U_("First Plugin"));
	VERIFY_EQUAL_NONCONT(plug.fDryRatio, 0.26f);
	VERIFY_EQUAL_NONCONT(plug.IsMasterEffect(), true);
	VERIFY_EQUAL_NONCONT(plug.GetGain(), 11);
}


// Check if our test file was loaded correctly.
static void TestLoadMPTMFile(const CSoundFile &sndFile)
{

	// Global Variables
	VERIFY_EQUAL_NONCONT(sndFile.GetTitle(), "Test Module_____________X");
	VERIFY_EQUAL_NONCONT(sndFile.m_songMessage.substr(0, 32), "OpenMPT Module Loader Test Suite");
	VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultGlobalVolume, 128);
	VERIFY_EQUAL_NONCONT(sndFile.m_nVSTiVolume, 42);
	VERIFY_EQUAL_NONCONT(sndFile.m_nSamplePreAmp, 23);
	VERIFY_EQUAL_NONCONT(sndFile.m_SongFlags, SONG_LINEARSLIDES | SONG_EXFILTERRANGE | SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS);
	VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[MSF_COMPATIBLE_PLAY], true);
	VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[kMIDICCBugEmulation], false);
	VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[kMPTOldSwingBehaviour], false);
	VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[kOldMIDIPitchBends], false);
	VERIFY_EQUAL_NONCONT(sndFile.GetMixLevels(), MixLevels::Compatible);
	VERIFY_EQUAL_NONCONT(sndFile.m_nTempoMode, TempoMode::Modern);
	VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultRowsPerBeat, 6);
	VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultRowsPerMeasure, 12);
	VERIFY_EQUAL_NONCONT(sndFile.m_dwCreatedWithVersion, MPT_V("1.19.02.05"));
	VERIFY_EQUAL_NONCONT(sndFile.m_nResampling, SRCMODE_SINC8LP);
	VERIFY_EQUAL_NONCONT(sndFile.m_songArtist, U_("Tester"));
	VERIFY_EQUAL_NONCONT(sndFile.m_tempoSwing.size(), 6);
	VERIFY_EQUAL_NONCONT(sndFile.m_tempoSwing[0], 29360125);
	VERIFY_EQUAL_NONCONT(sndFile.m_tempoSwing[1], 4194305);
	VERIFY_EQUAL_NONCONT(sndFile.m_tempoSwing[2], 29360128);
	VERIFY_EQUAL_NONCONT(sndFile.m_tempoSwing[3], 4194305);
	VERIFY_EQUAL_NONCONT(sndFile.m_tempoSwing[4], 29360128);
	VERIFY_EQUAL_NONCONT(sndFile.m_tempoSwing[5], 4194305);

	// Edit history
	VERIFY_EQUAL_NONCONT(sndFile.GetFileHistory().size() > 15, true);
	const FileHistory &fh = sndFile.GetFileHistory().front();
#ifdef MODPLUG_TRACKER
	if(sndFile.GetTimezoneInternal() == mpt::Date::LogicalTimezone::UTC)
	{
		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::local_from_default(mpt::Date::default_from_UTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).year, 2011);
		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::local_from_default(mpt::Date::default_from_UTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).month, 6);
		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::local_from_default(mpt::Date::default_from_UTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).day, 14);
#if MPT_CXX_AT_LEAST(20) && !defined(MPT_LIBCXX_QUIRK_NO_CHRONO) && !defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE)
		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::local_from_default(mpt::Date::default_from_UTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).hours, 21);
#else
#if defined(MPT_FALLBACK_TIMEZONE_WINDOWS_HISTORIC)
		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::local_from_default(mpt::Date::default_from_UTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).hours, 21);
#elif defined(MPT_FALLBACK_TIMEZONE_WINDOWS_CURRENT)
		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::local_from_default(mpt::Date::default_from_UTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).hours, 21);
#elif defined(MPT_FALLBACK_TIMEZONE_C)
		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::local_from_default(mpt::Date::default_from_UTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).hours, 22);
#else
		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::local_from_default(mpt::Date::default_from_UTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).hours, 22);
#endif
#endif
		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::local_from_default(mpt::Date::default_from_UTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).minutes, 8);
		VERIFY_EQUAL_NONCONT(mpt::Date::forget_timezone(mpt::Date::local_from_default(mpt::Date::default_from_UTC(mpt::Date::interpret_as_timezone<mpt::Date::LogicalTimezone::UTC>(fh.loadDate)))).seconds, 32);
	} else
#endif // MODPLUG_TRACKER
	{
		VERIFY_EQUAL_NONCONT(fh.loadDate.year, 2011);
		VERIFY_EQUAL_NONCONT(fh.loadDate.month, 6);
		VERIFY_EQUAL_NONCONT(fh.loadDate.day, 14);
		VERIFY_EQUAL_NONCONT(fh.loadDate.hours, 21);
		VERIFY_EQUAL_NONCONT(fh.loadDate.minutes, 8);
		VERIFY_EQUAL_NONCONT(fh.loadDate.seconds, 32);
	}
	VERIFY_EQUAL_NONCONT((uint32)((double)fh.openTime / HISTORY_TIMER_PRECISION), 31);

	// Macros
	VERIFY_EQUAL_NONCONT(sndFile.m_MidiCfg.GetParameteredMacroType(0), kSFxReso);
	VERIFY_EQUAL_NONCONT(sndFile.m_MidiCfg.GetParameteredMacroType(1), kSFxDryWet);
	VERIFY_EQUAL_NONCONT(sndFile.m_MidiCfg.GetParameteredMacroType(2), kSFxPolyAT);
	VERIFY_EQUAL_NONCONT(sndFile.m_MidiCfg.GetFixedMacroType(), kZxxResoFltMode);

	// Channels
	VERIFY_EQUAL_NONCONT(sndFile.GetNumChannels(), 70);
	VERIFY_EQUAL_NONCONT((sndFile.ChnSettings[0].szName == "First Channel"), true);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[0].nPan, 32);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[0].nVolume, 32);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[0].dwFlags, CHN_MUTE);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[0].nMixPlugin, 0);

	VERIFY_EQUAL_NONCONT((sndFile.ChnSettings[1].szName == "Second Channel"), true);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[1].nPan, 128);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[1].nVolume, 16);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[1].dwFlags, CHN_SURROUND);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[1].nMixPlugin, 1);

	VERIFY_EQUAL_NONCONT((sndFile.ChnSettings[69].szName == "Last Channel______X"), true);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[69].nPan, 256);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[69].nVolume, 7);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[69].dwFlags, ChannelFlags(0));
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[69].nMixPlugin, 1);
	// Samples
	VERIFY_EQUAL_NONCONT(sndFile.GetNumSamples(), 4);
	{
		const ModSample &sample = sndFile.GetSample(1);
		VERIFY_EQUAL_NONCONT(sample.GetBytesPerSample(), 1);
		VERIFY_EQUAL_NONCONT(sample.GetNumChannels(), 1);
		VERIFY_EQUAL_NONCONT(sample.GetElementarySampleSize(), 1);
		VERIFY_EQUAL_NONCONT(sample.GetSampleSizeInBytes(), 16);
		VERIFY_EQUAL_NONCONT(sample.GetSampleRate(MOD_TYPE_MPT), 9001);
		VERIFY_EQUAL_NONCONT(sample.nVolume, 32 * 4);
		VERIFY_EQUAL_NONCONT(sample.nGlobalVol, 16);
		VERIFY_EQUAL_NONCONT(sample.nPan, 160);
		VERIFY_EQUAL_NONCONT(sample.uFlags, CHN_PANNING | CHN_LOOP | CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN);

		VERIFY_EQUAL_NONCONT(sample.nLoopStart, 1);
		VERIFY_EQUAL_NONCONT(sample.nLoopEnd, 8);
		VERIFY_EQUAL_NONCONT(sample.nSustainStart, 1);
		VERIFY_EQUAL_NONCONT(sample.nSustainEnd, 7);

		VERIFY_EQUAL_NONCONT(sample.nVibType, VIB_SQUARE);
		VERIFY_EQUAL_NONCONT(sample.nVibSweep, 3);
		VERIFY_EQUAL_NONCONT(sample.nVibRate, 4);
		VERIFY_EQUAL_NONCONT(sample.nVibDepth, 5);

		// Sample Data
		for(size_t i = 0; i < 6; i++)
		{
			VERIFY_EQUAL_NONCONT(sample.sample8()[i], 18);
		}
		for(size_t i = 6; i < 16; i++)
		{
			VERIFY_EQUAL_NONCONT(sample.sample8()[i], 0);
		}

		VERIFY_EQUAL_NONCONT(sample.cues[0], 2);
		VERIFY_EQUAL_NONCONT(sample.cues[8], 9);
	}

	{
		const ModSample &sample = sndFile.GetSample(2);
		VERIFY_EQUAL_NONCONT((sndFile.m_szNames[2] == "Stereo / 16-Bit"), true);
		VERIFY_EQUAL_NONCONT(sample.GetBytesPerSample(), 4);
		VERIFY_EQUAL_NONCONT(sample.GetNumChannels(), 2);
		VERIFY_EQUAL_NONCONT(sample.GetElementarySampleSize(), 2);
		VERIFY_EQUAL_NONCONT(sample.GetSampleSizeInBytes(), 16 * 4);
		VERIFY_EQUAL_NONCONT(sample.GetSampleRate(MOD_TYPE_MPT), 16000);
		VERIFY_EQUAL_NONCONT(sample.uFlags, CHN_16BIT | CHN_STEREO | CHN_LOOP);

		// Sample Data (Stereo Interleaved)
		for(size_t i = 0; i < 7; i++)
		{
			VERIFY_EQUAL_NONCONT(sample.sample16()[4 + i], int16(-32768));
		}

		VERIFY_EQUAL_NONCONT(sample.cues[0], 3);
		VERIFY_EQUAL_NONCONT(sample.cues[8], 14);
	}

	// External sample
	{
		const ModSample &sample = sndFile.GetSample(4);
		VERIFY_EQUAL_NONCONT((sndFile.m_szNames[4] == "Overridden Name"), true);
		VERIFY_EQUAL_NONCONT((sample.filename == "External"), true);
#ifdef MPT_EXTERNAL_SAMPLES
		VERIFY_EQUAL_NONCONT(sample.GetBytesPerSample(), 1);
		VERIFY_EQUAL_NONCONT(sample.GetNumChannels(), 1);
		VERIFY_EQUAL_NONCONT(sample.GetElementarySampleSize(), 1);
		VERIFY_EQUAL_NONCONT(sample.GetSampleSizeInBytes(), 64);

		VERIFY_EQUAL_NONCONT(sample.nLoopStart, 42);
		VERIFY_EQUAL_NONCONT(sample.nLoopEnd, 55);
		VERIFY_EQUAL_NONCONT(sample.nSustainStart, 42);
		VERIFY_EQUAL_NONCONT(sample.nSustainEnd, 55);
		VERIFY_EQUAL_NONCONT(sample.uFlags, CHN_LOOP | CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN | SMP_KEEPONDISK);
#endif // MPT_EXTERNAL_SAMPLES
		VERIFY_EQUAL_NONCONT(sample.GetSampleRate(MOD_TYPE_MPT), 10101);
		VERIFY_EQUAL_NONCONT(sample.nVolume, 26 * 4);
		VERIFY_EQUAL_NONCONT(sample.nGlobalVol, 26);
		VERIFY_EQUAL_NONCONT(sample.nPan, 26 * 4);

		VERIFY_EQUAL_NONCONT(sample.nVibType, VIB_SINE);
		VERIFY_EQUAL_NONCONT(sample.nVibSweep, 37);
		VERIFY_EQUAL_NONCONT(sample.nVibRate, 42);
		VERIFY_EQUAL_NONCONT(sample.nVibDepth, 23);

		// Sample Data
#ifdef MPT_EXTERNAL_SAMPLES
		for(size_t i = 0; i < 16; i++)
		{
			VERIFY_EQUAL_NONCONT(sample.sample8()[i], int8(45));
		}
#endif // MPT_EXTERNAL_SAMPLES

		VERIFY_EQUAL_NONCONT(sample.cues[0], 10);
		VERIFY_EQUAL_NONCONT(sample.cues[8], 50);
	}

	// Instruments
	VERIFY_EQUAL_NONCONT(sndFile.GetNumInstruments(), 2);
	for(INSTRUMENTINDEX ins = 1; ins <= 2; ins++)
	{
		const ModInstrument *pIns = sndFile.Instruments[ins];
		VERIFY_EQUAL_NONCONT(pIns->nGlobalVol, 32);
		VERIFY_EQUAL_NONCONT(pIns->nFadeOut, (ins == 1) ? 1023u : 8992u);
		VERIFY_EQUAL_NONCONT(pIns->nPan, 63);
		VERIFY_EQUAL_NONCONT(pIns->dwFlags, INS_SETPANNING);

		VERIFY_EQUAL_NONCONT(pIns->nPPS, 8);
		VERIFY_EQUAL_NONCONT(pIns->nPPC, (NOTE_MIDDLEC - NOTE_MIN) + 6);	// F#5

		VERIFY_EQUAL_NONCONT(pIns->nVolRampUp, 1200);
		VERIFY_EQUAL_NONCONT(pIns->resampling, SRCMODE_SINC8LP);

		VERIFY_EQUAL_NONCONT(pIns->IsCutoffEnabled(), true);
		VERIFY_EQUAL_NONCONT(pIns->GetCutoff(), 0x32);
		VERIFY_EQUAL_NONCONT(pIns->IsResonanceEnabled(), true);
		VERIFY_EQUAL_NONCONT(pIns->GetResonance(), 0x64);
		VERIFY_EQUAL_NONCONT(pIns->filterMode, FilterMode::HighPass);

		VERIFY_EQUAL_NONCONT(pIns->nVolSwing, 0x30);
		VERIFY_EQUAL_NONCONT(pIns->nPanSwing, 0x18);
		VERIFY_EQUAL_NONCONT(pIns->nCutSwing, 0x0C);
		VERIFY_EQUAL_NONCONT(pIns->nResSwing, 0x3C);

		VERIFY_EQUAL_NONCONT(pIns->nNNA, NewNoteAction::Continue);
		VERIFY_EQUAL_NONCONT(pIns->nDCT, DuplicateCheckType::Note);
		VERIFY_EQUAL_NONCONT(pIns->nDNA, DuplicateNoteAction::NoteFade);

		VERIFY_EQUAL_NONCONT(pIns->nMixPlug, 1);
		VERIFY_EQUAL_NONCONT(pIns->nMidiChannel, 16);
		VERIFY_EQUAL_NONCONT(pIns->nMidiProgram, 64);
		VERIFY_EQUAL_NONCONT(pIns->wMidiBank, 2);
		VERIFY_EQUAL_NONCONT(pIns->midiPWD, -1);

		if(ins == 1)
			VERIFY_EQUAL_NONCONT(pIns->pTuning, nullptr);
		else
			VERIFY_EQUAL_NONCONT(pIns->pTuning->GetName(), UL_("Test Tuning"));

		VERIFY_EQUAL_NONCONT(pIns->pitchToTempoLock, TEMPO(130, 2000));

		VERIFY_EQUAL_NONCONT(pIns->pluginVelocityHandling, PLUGIN_VELOCITYHANDLING_VOLUME);
		VERIFY_EQUAL_NONCONT(pIns->pluginVolumeHandling, PLUGIN_VOLUMEHANDLING_MIDI);

		for(size_t i = 0; i < 120; i++)
		{
			VERIFY_EQUAL_NONCONT(pIns->Keyboard[i], (i == NOTE_MIDDLEC - 1) ? (ins * 1111) : 1);
			VERIFY_EQUAL_NONCONT(pIns->NoteMap[i], (i == NOTE_MIDDLEC - 1) ? (i + 13) : (i + 1));
		}

		VERIFY_EQUAL_NONCONT(pIns->VolEnv.dwFlags, ENV_ENABLED | ENV_CARRY);
		VERIFY_EQUAL_NONCONT(pIns->VolEnv.size(), 3);
		VERIFY_EQUAL_NONCONT(pIns->VolEnv.nReleaseNode, 1);
		VERIFY_EQUAL_NONCONT(pIns->VolEnv[2].tick, 96);
		VERIFY_EQUAL_NONCONT(pIns->VolEnv[2].value, 0);

		VERIFY_EQUAL_NONCONT(pIns->PanEnv.dwFlags, ENV_LOOP | ENV_SUSTAIN);
		VERIFY_EQUAL_NONCONT(pIns->PanEnv.size(), (ins == 1) ? 74u : 76u);
		VERIFY_EQUAL_NONCONT(pIns->PanEnv.nLoopStart, 26);
		VERIFY_EQUAL_NONCONT(pIns->PanEnv.nLoopEnd, 29);
		VERIFY_EQUAL_NONCONT(pIns->PanEnv.nSustainStart, 27);
		VERIFY_EQUAL_NONCONT(pIns->PanEnv.nSustainEnd, 28);
		VERIFY_EQUAL_NONCONT(pIns->PanEnv.nReleaseNode, ENV_RELEASE_NODE_UNSET);
		VERIFY_EQUAL_NONCONT(pIns->PanEnv[73].tick, 417);
		VERIFY_EQUAL_NONCONT(pIns->PanEnv[73].value, 23);
		if(ins == 2)
		{
			VERIFY_EQUAL_NONCONT(pIns->PanEnv[75].tick, 427);
			VERIFY_EQUAL_NONCONT(pIns->PanEnv[75].value, 27);
		}

		VERIFY_EQUAL_NONCONT(pIns->PitchEnv.dwFlags, ENV_ENABLED | ENV_CARRY | ENV_SUSTAIN | ENV_FILTER);
		VERIFY_EQUAL_NONCONT(pIns->PitchEnv.size(), 3);
		VERIFY_EQUAL_NONCONT(pIns->PitchEnv.nSustainStart, 1);
		VERIFY_EQUAL_NONCONT(pIns->PitchEnv.nSustainEnd, 2);
		VERIFY_EQUAL_NONCONT(pIns->PitchEnv[1].tick, 96);
		VERIFY_EQUAL_NONCONT(pIns->PitchEnv[1].value, 64);
	}
	// Sequences
	VERIFY_EQUAL_NONCONT(sndFile.Order.GetNumSequences(), 2);

	VERIFY_EQUAL_NONCONT(sndFile.Order(0).GetLengthTailTrimmed(), 3);
	VERIFY_EQUAL_NONCONT(sndFile.Order(0).GetName(), U_("First Sequence"));
	VERIFY_EQUAL_NONCONT(sndFile.Order(0)[0], PATTERNINDEX_SKIP);
	VERIFY_EQUAL_NONCONT(sndFile.Order(0)[1], 0);
	VERIFY_EQUAL_NONCONT(sndFile.Order(0)[2], PATTERNINDEX_SKIP);
	VERIFY_EQUAL_NONCONT(sndFile.Order(0).GetRestartPos(), 1);
	VERIFY_EQUAL_NONCONT(sndFile.Order(0).GetDefaultTempo(), TEMPO(139, 999));
	VERIFY_EQUAL_NONCONT(sndFile.Order(0).GetDefaultSpeed(), 5);

	VERIFY_EQUAL_NONCONT(sndFile.Order(1).GetLengthTailTrimmed(), 3);
	VERIFY_EQUAL_NONCONT(sndFile.Order(1).GetName(), U_("Second Sequence"));
	VERIFY_EQUAL_NONCONT(sndFile.Order(1)[0], 1);
	VERIFY_EQUAL_NONCONT(sndFile.Order(1)[1], 2);
	VERIFY_EQUAL_NONCONT(sndFile.Order(1)[2], 3);
	VERIFY_EQUAL_NONCONT(sndFile.Order(1).GetRestartPos(), 2);
	VERIFY_EQUAL_NONCONT(sndFile.Order(1).GetDefaultTempo(), TEMPO(123, 4500));
	VERIFY_EQUAL_NONCONT(sndFile.Order(1).GetDefaultSpeed(), 67);

	// Patterns
	VERIFY_EQUAL_NONCONT(sndFile.Patterns.GetNumPatterns(), 2);

	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetName(), "First Pattern");
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetNumRows(), 70);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetNumChannels(), 70);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetOverrideSignature(), true);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetRowsPerBeat(), 5);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetRowsPerMeasure(), 10);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].HasTempoSwing(), true);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].HasColor(), false);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns.IsPatternEmpty(0), true);

	{
		TempoSwing swing = sndFile.Patterns[0].GetTempoSwing();
		VERIFY_EQUAL_NONCONT(swing.size(), 5);
		VERIFY_EQUAL_NONCONT(swing[0], 16770149);
		VERIFY_EQUAL_NONCONT(swing[1], 16803696);
		VERIFY_EQUAL_NONCONT(swing[2], 16770157);
		VERIFY_EQUAL_NONCONT(swing[3], 29347774);
		VERIFY_EQUAL_NONCONT(swing[4], 4194304);
	}

	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetName(), "Second Pattern");
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetNumRows(), 32);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetNumChannels(), 70);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetOverrideSignature(), false);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetRowsPerBeat(), 0);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetRowsPerMeasure(), 0);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].HasTempoSwing(), false);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetColor(), 0xFFC080);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns.IsPatternEmpty(1), false);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 0)->IsPcNote(), true);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 0)->note, NOTE_PC);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 0)->instr, 99);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 0)->GetValueVolCol(), 1);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 0)->GetValueEffectCol(), 200);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(1, 0)->IsPcNote(), true);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(1, 0)->note, NOTE_PCS);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(1, 0)->instr, 250);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(1, 0)->GetValueVolCol(), 999);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(1, 0)->GetValueEffectCol(), 999);

	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 1)->note, NOTE_MIN);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(1, 1)->note, NOTE_MIN + 119);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(2, 1)->note, NOTE_KEYOFF);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(3, 1)->note, NOTE_NOTECUT);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(4, 1)->note, NOTE_FADE);

	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 0)->IsEmpty(), true);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->IsEmpty(), false);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->IsPcNote(), false);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->note, NOTE_MIDDLEC + 12);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->instr, 45);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->volcmd, VOLCMD_VOLSLIDEDOWN);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->vol, 5);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->command, CMD_PANNING8);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->param, 0xFF);

	// Plugins
	const SNDMIXPLUGIN &plug = sndFile.m_MixPlugins[0];
	VERIFY_EQUAL_NONCONT(plug.GetName(), U_("First Plugin"));
	VERIFY_EQUAL_NONCONT(plug.fDryRatio, 0.26f);
	VERIFY_EQUAL_NONCONT(plug.IsMasterEffect(), true);
	VERIFY_EQUAL_NONCONT(plug.GetGain(), 11);
	VERIFY_EQUAL_NONCONT(plug.pMixPlugin != nullptr, true);
	if(plug.pMixPlugin)
	{
		VERIFY_EQUAL_NONCONT(plug.pMixPlugin->GetParameter(1), 0.5f);
		VERIFY_EQUAL_NONCONT(plug.pMixPlugin->IsInstrument(), false);
	}

#ifdef MODPLUG_TRACKER
	// MIDI Mapping
	VERIFY_EQUAL_NONCONT(sndFile.GetMIDIMapper().GetCount(), 1);
	const CMIDIMappingDirective &mapping = sndFile.GetMIDIMapper().GetDirective(0);
	VERIFY_EQUAL_NONCONT(mapping.GetAllowPatternEdit(), true);
	VERIFY_EQUAL_NONCONT(mapping.GetCaptureMIDI(), false);
	VERIFY_EQUAL_NONCONT(mapping.IsActive(), true);
	VERIFY_EQUAL_NONCONT(mapping.GetAnyChannel(), false);
	VERIFY_EQUAL_NONCONT(mapping.GetChannel(), 5);
	VERIFY_EQUAL_NONCONT(mapping.GetPlugIndex(), 1);
	VERIFY_EQUAL_NONCONT(mapping.GetParamIndex(), 0);
	VERIFY_EQUAL_NONCONT(mapping.GetEvent(), MIDIEvents::evControllerChange);
	VERIFY_EQUAL_NONCONT(mapping.GetController(), MIDIEvents::MIDICC_ModulationWheel_Coarse);

	// Channel colors
	const auto &chns = sndFile.ChnSettings;
	VERIFY_EQUAL_NONCONT(chns[0].color, RGB(255, 0, 0));
	VERIFY_EQUAL_NONCONT(chns[1].color, RGB(0, 255, 0));
	VERIFY_EQUAL_NONCONT(chns[2].color, RGB(0, 0, 255));
	VERIFY_EQUAL_NONCONT(chns[3].color, ModChannelSettings::INVALID_COLOR);
	VERIFY_EQUAL_NONCONT(chns[67].color, RGB(255, 0, 255));
	VERIFY_EQUAL_NONCONT(chns[68].color, RGB(255, 255, 0));
	VERIFY_EQUAL_NONCONT(chns[69].color, ModChannelSettings::INVALID_COLOR);
#endif

	VERIFY_EQUAL_NONCONT(sndFile.FrequencyToCutOff(static_cast<double>(sndFile.CutOffToFrequency(0))), 0);
	VERIFY_EQUAL_NONCONT(sndFile.FrequencyToCutOff(static_cast<double>(sndFile.CutOffToFrequency(80))), 80);
	VERIFY_EQUAL_NONCONT(sndFile.FrequencyToCutOff(static_cast<double>(sndFile.CutOffToFrequency(127))), 127);
}


// Check if our test file was loaded correctly.
static void TestLoadS3MFile(const CSoundFile &sndFile, bool resaved)
{

	// Global Variables
	VERIFY_EQUAL_NONCONT(sndFile.GetTitle(), "S3M_Test__________________X");
	VERIFY_EQUAL_NONCONT(sndFile.Order().GetDefaultTempo(), TEMPO(33, 0));
	VERIFY_EQUAL_NONCONT(sndFile.Order().GetDefaultSpeed(), 254);
	VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultGlobalVolume, 32 * 4);
	VERIFY_EQUAL_NONCONT(sndFile.m_nVSTiVolume, 36);
	VERIFY_EQUAL_NONCONT(sndFile.m_nSamplePreAmp, 16);
	VERIFY_EQUAL_NONCONT(sndFile.m_SongFlags, SONG_FASTVOLSLIDES);
	VERIFY_EQUAL_NONCONT(sndFile.GetMixLevels(), MixLevels::Compatible);
	VERIFY_EQUAL_NONCONT(sndFile.m_nTempoMode, TempoMode::Classic);
	VERIFY_EQUAL_NONCONT(sndFile.m_dwLastSavedWithVersion, resaved ? Version::Current() : MPT_V("1.32.00.32"));
	VERIFY_EQUAL_NONCONT(sndFile.GetFileHistory().size(), 1);

	// Channels
	VERIFY_EQUAL_NONCONT(sndFile.GetNumChannels(), 4);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[0].nPan, 0);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[0].dwFlags, ChannelFlags(0));

	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[1].nPan, 256);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[1].dwFlags, CHN_MUTE);

	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[2].nPan, 85);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[2].dwFlags, ChannelFlags(0));

	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[3].nPan, 171);
	VERIFY_EQUAL_NONCONT(sndFile.ChnSettings[3].dwFlags, CHN_MUTE);

	// Samples
	VERIFY_EQUAL_NONCONT(sndFile.GetNumSamples(), 4);
	{
		const ModSample &sample = sndFile.GetSample(1);
		VERIFY_EQUAL_NONCONT(sndFile.m_szNames[1], "Sample_1__________________X");
		VERIFY_EQUAL_NONCONT(sample.filename, "Filename_1_X");
		VERIFY_EQUAL_NONCONT(sample.GetBytesPerSample(), 1);
		VERIFY_EQUAL_NONCONT(sample.GetNumChannels(), 1);
		VERIFY_EQUAL_NONCONT(sample.GetElementarySampleSize(), 1);
		VERIFY_EQUAL_NONCONT(sample.GetSampleSizeInBytes(), 60);
		VERIFY_EQUAL_NONCONT(sample.GetSampleRate(MOD_TYPE_S3M), 9001);
		VERIFY_EQUAL_NONCONT(sample.nVolume, 32 * 4);
		VERIFY_EQUAL_NONCONT(sample.nGlobalVol, 64);
		VERIFY_EQUAL_NONCONT(sample.uFlags, CHN_LOOP);

		VERIFY_EQUAL_NONCONT(sample.nLoopStart, 16);
		VERIFY_EQUAL_NONCONT(sample.nLoopEnd, 60);

		// Sample Data
		for(size_t i = 0; i < 30; i++)
		{
			VERIFY_EQUAL_NONCONT(sample.sample8()[i], 127);
		}
		for(size_t i = 31; i < 60; i++)
		{
			VERIFY_EQUAL_NONCONT(sample.sample8()[i], -128);
		}
	}

	{
		const ModSample &sample = sndFile.GetSample(2);
		VERIFY_EQUAL_NONCONT(sndFile.m_szNames[2], "Empty");
		VERIFY_EQUAL_NONCONT(sample.GetSampleRate(MOD_TYPE_S3M), 16384);
		VERIFY_EQUAL_NONCONT(sample.nVolume, 2 * 4);
	}

	{
		const ModSample &sample = sndFile.GetSample(3);
		VERIFY_EQUAL_NONCONT(sndFile.m_szNames[3], "Stereo / 16-Bit");
		VERIFY_EQUAL_NONCONT(sample.filename, "Filename_3_X");
		VERIFY_EQUAL_NONCONT(sample.GetBytesPerSample(), 4);
		VERIFY_EQUAL_NONCONT(sample.GetNumChannels(), 2);
		VERIFY_EQUAL_NONCONT(sample.GetElementarySampleSize(), 2);
		VERIFY_EQUAL_NONCONT(sample.GetSampleSizeInBytes(), 64);
		VERIFY_EQUAL_NONCONT(sample.GetSampleRate(MOD_TYPE_S3M), 16000);
		VERIFY_EQUAL_NONCONT(sample.nVolume, 0);
		VERIFY_EQUAL_NONCONT(sample.uFlags, CHN_LOOP | CHN_16BIT | CHN_STEREO);

		VERIFY_EQUAL_NONCONT(sample.nLoopStart, 0);
		VERIFY_EQUAL_NONCONT(sample.nLoopEnd, 16);

		// Sample Data (Stereo Interleaved)
		for(size_t i = 0; i < 7; i++)
		{
			VERIFY_EQUAL_NONCONT(sample.sample16()[4 + i], int16(-32768));
		}
	}

	{
		const ModSample &sample = sndFile.GetSample(4);
		VERIFY_EQUAL_NONCONT(sndFile.m_szNames[4], "adlib");
		VERIFY_EQUAL_NONCONT((sample.filename == ""), true);
		VERIFY_EQUAL_NONCONT(sample.GetSampleRate(MOD_TYPE_S3M), 8363);
		VERIFY_EQUAL_NONCONT(sample.nVolume, 58 * 4);
		VERIFY_EQUAL_NONCONT(sample.uFlags, CHN_ADLIB);
		VERIFY_EQUAL_NONCONT(sample.adlib, (OPLPatch{ { 0x00, 0x00, 0xC0, 0x00, 0xF0, 0xD2, 0x05, 0xB3, 0x01, 0x00, 0x00, 0x00 } }));
	}

	// Orders
	VERIFY_EQUAL_NONCONT(sndFile.Order().GetRestartPos(), 0);
	VERIFY_EQUAL_NONCONT(sndFile.Order().GetLengthTailTrimmed(), 5);
	VERIFY_EQUAL_NONCONT(sndFile.Order()[0], 0);
	VERIFY_EQUAL_NONCONT(sndFile.Order()[1], PATTERNINDEX_SKIP);
	VERIFY_EQUAL_NONCONT(sndFile.Order()[2], PATTERNINDEX_INVALID);
	VERIFY_EQUAL_NONCONT(sndFile.Order()[3], 1);
	VERIFY_EQUAL_NONCONT(sndFile.Order()[4], 0);

	// Patterns
	VERIFY_EQUAL_NONCONT(sndFile.Patterns.GetNumPatterns(), 2);

	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetNumRows(), 64);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetNumChannels(), 4);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetOverrideSignature(), false);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(0, 0)->note, NOTE_MIN + 12);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(1, 0)->note, NOTE_MIN + 107);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(2, 0)->note, NOTE_NOTECUT);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(0, 1)->volcmd, VOLCMD_VOLUME);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(0, 1)->vol, 0);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(1, 1)->volcmd, VOLCMD_VOLUME);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(1, 1)->vol, 64);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(2, 1)->volcmd, VOLCMD_PANNING);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(2, 1)->vol, 0);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(3, 1)->volcmd, VOLCMD_PANNING);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(3, 1)->vol, 64);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(0, 3)->command, CMD_SPEED);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(0, 3)->param, 0x11);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(3, 0)->IsEmpty(), false);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(3, 0)->note, NOTE_NONE);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(3, 0)->instr, 99);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(4, 0)->IsEmpty(), true);  // Test for resaved out-of-range note
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(5, 0)->IsEmpty(), true);  // Test for resaved out-of-range note

	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetNumRows(), 64);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns.IsPatternEmpty(1), false);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(63, 3)->param, 0x04);
}


// Check if our test file was loaded correctly.
static void TestLoadMODFile(CSoundFile &sndFile)
{
	// Global Variables
	VERIFY_EQUAL_NONCONT(sndFile.GetTitle(), "MOD_Test___________X");
	VERIFY_EQUAL_NONCONT(sndFile.m_SongFlags, SONG_PT_MODE | SONG_AMIGALIMITS | SONG_ISAMIGA | SONG_FORMAT_NO_VOLCOL);
	VERIFY_EQUAL_NONCONT(sndFile.GetMixLevels(), MixLevels::Compatible);
	VERIFY_EQUAL_NONCONT(sndFile.m_nTempoMode, TempoMode::Classic);
	VERIFY_EQUAL_NONCONT(sndFile.GetNumChannels(), 4);
	VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[kMODOneShotLoops], true);
	VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[kMODSampleSwap], true);
	VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[kMODIgnorePanning], true);
	VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[kMODVBlankTiming], false);

	// Test GetLength code, in particular with subsongs
	VERIFY_EQUAL_NONCONT(sndFile.GetLength(eNoAdjust, GetLengthTarget(0, 4)).back().targetReached, false);

	const auto allSubSongs = sndFile.GetLength(eNoAdjust, GetLengthTarget(true));
	VERIFY_EQUAL_NONCONT(allSubSongs.size(), 2);
	VERIFY_EQUAL_EPS(allSubSongs[0].duration, 2.04, 0.1);
	VERIFY_EQUAL_EPS(allSubSongs[1].duration, 118.84, 0.1);
	VERIFY_EQUAL_NONCONT(allSubSongs[0].restartOrder, 0);
	VERIFY_EQUAL_NONCONT(allSubSongs[0].restartRow, 1);
	VERIFY_EQUAL_NONCONT(allSubSongs[1].restartOrder, 2);
	VERIFY_EQUAL_NONCONT(allSubSongs[1].restartRow, 61);
	VERIFY_EQUAL_NONCONT(allSubSongs[1].startOrder, 2);
	VERIFY_EQUAL_NONCONT(allSubSongs[1].startRow, 0);

	// Samples
	VERIFY_EQUAL_NONCONT(sndFile.GetNumSamples(), 31);
	{
		const ModSample &sample = sndFile.GetSample(1);
		VERIFY_EQUAL_NONCONT(sndFile.m_szNames[1], "Sample_1_____________X");
		VERIFY_EQUAL_NONCONT(sample.GetBytesPerSample(), 1);
		VERIFY_EQUAL_NONCONT(sample.GetNumChannels(), 1);
		VERIFY_EQUAL_NONCONT(sample.GetElementarySampleSize(), 1);
		VERIFY_EQUAL_NONCONT(sample.GetSampleSizeInBytes(), 1244);
		VERIFY_EQUAL_NONCONT(sample.nFineTune, 0x70);
		VERIFY_EQUAL_NONCONT(sample.RelativeTone, 0);
		VERIFY_EQUAL_NONCONT(sample.nVolume, 256);
		VERIFY_EQUAL_NONCONT(sample.nGlobalVol, 64);
		VERIFY_EQUAL_NONCONT(sample.uFlags, CHN_LOOP);

		VERIFY_EQUAL_NONCONT(sample.nLoopStart, 0);
		VERIFY_EQUAL_NONCONT(sample.nLoopEnd, 128);

		// Sample Data
		VERIFY_EQUAL_NONCONT(sample.sample8()[0], 0);
		VERIFY_EQUAL_NONCONT(sample.sample8()[1], 0);
		VERIFY_EQUAL_NONCONT(sample.sample8()[2], -29);
	}
	{
		const ModSample &sample = sndFile.GetSample(3);
		VERIFY_EQUAL_NONCONT(sndFile.m_szNames[3], "OpenMPT Module Loader");
		VERIFY_EQUAL_NONCONT(sample.GetSampleSizeInBytes(), 0);
		VERIFY_EQUAL_NONCONT(sample.nFineTune, -0x80);
		VERIFY_EQUAL_NONCONT(sample.RelativeTone, 0);
		VERIFY_EQUAL_NONCONT(sample.nVolume, 4);
		VERIFY_EQUAL_NONCONT(sample.nGlobalVol, 64);
	}

	// Orders
	VERIFY_EQUAL_NONCONT(sndFile.Order().GetRestartPos(), 0);
	VERIFY_EQUAL_NONCONT(sndFile.Order().GetLengthTailTrimmed(), 4);
	VERIFY_EQUAL_NONCONT(sndFile.Order()[0], 0);
	VERIFY_EQUAL_NONCONT(sndFile.Order()[1], 1);
	VERIFY_EQUAL_NONCONT(sndFile.Order()[2], 2);
	VERIFY_EQUAL_NONCONT(sndFile.Order()[3], 0);

	// Patterns
	VERIFY_EQUAL_NONCONT(sndFile.Patterns.GetNumPatterns(), 3);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[2].GetNumRows(), 64);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[2].GetpModCommand(1, 0)->note, NOTE_MIDDLEC + 12);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[2].GetpModCommand(16, 3)->instr, 1);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[2].GetpModCommand(19, 3)->command, CMD_PANNING8);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[2].GetpModCommand(19, 3)->param, 0x28);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[2].GetpModCommand(20, 0)->command, CMD_TEMPO);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[2].GetpModCommand(20, 1)->command, CMD_SPEED);
}


#ifdef MODPLUG_TRACKER

static bool ShouldRunTests()
{
	mpt::PathString theFile = theApp.GetInstallPath();
	if(mpt::native_fs{}.is_directory(theFile) && mpt::native_fs{}.is_directory(theFile + P_("test")))
	{
		if(mpt::native_fs{}.is_file(theFile + P_("test\\test.mptm")))
		{
			return true;
		}
	}
	return false;
}

static mpt::PathString GetTestFilenameBase()
{
	mpt::PathString theFile = theApp.GetInstallPath();
	theFile += P_("test/test.");
	return theFile;
}

static mpt::PathString GetTempFilenameBase()
{
	return GetTestFilenameBase();
}

using TSoundFileContainer = CModDoc *;

static CSoundFile &GetSoundFile(TSoundFileContainer &sndFile)
{
	return sndFile->GetSoundFile();
}


static TSoundFileContainer CreateSoundFileContainer(const mpt::PathString &filename)
{
	CModDoc *pModDoc = static_cast<CModDoc *>(theApp.OpenDocumentFile(filename.ToCString(), FALSE));
	return pModDoc;
}

static void DestroySoundFileContainer(TSoundFileContainer &sndFile)
{
	sndFile->OnCloseDocument();
}

static void SaveTestFile(const TSoundFileContainer &sndFile, const mpt::PathString &filename)
{
	sndFile->DoSave(filename);
	// Saving the file puts it in the MRU list...
	theApp.RemoveMruItem(0);
}

const auto SaveIT = SaveTestFile;
const auto SaveXM = SaveTestFile;
const auto SaveS3M = SaveTestFile;
const auto SaveMOD = SaveTestFile;

#else // !MODPLUG_TRACKER

static bool ShouldRunTests()
{
	#if MPT_TEST_HAS_FILESYSTEM
		return true;
	#else
		return false;
	#endif
}

static mpt::PathString GetTestFilenameBase()
{
#if MPT_OS_DJGPP
	return Test::GetPathPrefix() + P_("test\\test.");
#else
	return Test::GetPathPrefix() + P_("./test/test.");
#endif
}

static mpt::PathString GetTempFilenameBase()
{
#if MPT_OS_DJGPP
	return P_("test.");
#else
	return P_("./test.");
#endif
}

using TSoundFileContainer = std::shared_ptr<CSoundFile>;

static CSoundFile &GetSoundFile(TSoundFileContainer &sndFile)
{
	return *sndFile.get();
}

static TSoundFileContainer CreateSoundFileContainer(const mpt::PathString &filename)
{
	mpt::IO::ifstream stream(filename, std::ios::binary);
	FileReader file = mpt::IO::make_FileCursor<mpt::PathString>(stream);
	std::shared_ptr<CSoundFile> pSndFile = std::make_shared<CSoundFile>();
	pSndFile->Create(file, CSoundFile::loadCompleteModule);
	return pSndFile;
}

static void DestroySoundFileContainer(TSoundFileContainer & /* sndFile */ )
{
	return;
}

#ifndef MODPLUG_NO_FILESAVE

static void SaveIT(const TSoundFileContainer &sndFile, const mpt::PathString &filename)
{
	mpt::IO::ofstream f(filename, std::ios::binary);
	sndFile->SaveIT(f, filename, false);
}

static void SaveXM(const TSoundFileContainer &sndFile, const mpt::PathString &filename)
{
	mpt::IO::ofstream f(filename, std::ios::binary);
	sndFile->SaveXM(f, false);
}

static void SaveS3M(const TSoundFileContainer &sndFile, const mpt::PathString &filename)
{
	mpt::IO::ofstream f(filename, std::ios::binary);
	sndFile->SaveS3M(f);
}

static void SaveMOD(const TSoundFileContainer &sndFile, const mpt::PathString &filename)
{
	mpt::IO::ofstream f(filename, std::ios::binary);
	sndFile->SaveMod(f);
}

#endif // !MODPLUG_NO_FILESAVE

#endif // MODPLUG_TRACKER



// Test file loading and saving
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestLoadSaveFile()
{
	if(!ShouldRunTests())
	{
		return;
	}

#ifdef MODPLUG_TRACKER
	bool saveMutedChannels = TrackerSettings::Instance().MiscSaveChannelMuteStatus;
	TrackerSettings::Instance().MiscSaveChannelMuteStatus = true;
#endif

	mpt::PathString filenameBaseSrc = GetTestFilenameBase();
	mpt::PathString filenameBase = GetTempFilenameBase();

	// Test MPTM file loading
	{
#if MPT_OS_DJGPP
		TSoundFileContainer sndFileContainer = CreateSoundFileContainer(filenameBaseSrc + P_("mpt"));
#else
		TSoundFileContainer sndFileContainer = CreateSoundFileContainer(filenameBaseSrc + P_("mptm"));
#endif

		TestLoadMPTMFile(GetSoundFile(sndFileContainer));

		#ifndef MODPLUG_NO_FILESAVE
			// Test file saving
			GetSoundFile(sndFileContainer).m_dwLastSavedWithVersion = Version::Current();
#if MPT_OS_DJGPP
			SaveIT(sndFileContainer, filenameBase + P_("spt"));
#else
			SaveIT(sndFileContainer, filenameBase + P_("saved.mptm"));
#endif
		#endif

		DestroySoundFileContainer(sndFileContainer);
	}

	// Reload the saved file and test if everything is still working correctly.
	#ifndef MODPLUG_NO_FILESAVE
	{
#if MPT_OS_DJGPP
		TSoundFileContainer sndFileContainer = CreateSoundFileContainer(filenameBase + P_("spt"));
#else
		TSoundFileContainer sndFileContainer = CreateSoundFileContainer(filenameBase + P_("saved.mptm"));
#endif

		TestLoadMPTMFile(GetSoundFile(sndFileContainer));

		DestroySoundFileContainer(sndFileContainer);

#if MPT_OS_DJGPP
		RemoveFile(filenameBase + P_("spt"));
#else
		RemoveFile(filenameBase + P_("saved.mptm"));
#endif
	}
	#endif

	// Test XM file loading
	{
		TSoundFileContainer sndFileContainer = CreateSoundFileContainer(filenameBaseSrc + P_("xm"));

		TestLoadXMFile(GetSoundFile(sndFileContainer));

		// In OpenMPT 1.20 (up to revision 1459), there was a bug in the XM saver
		// that would create broken XMs if the sample map contained samples that
		// were only referenced below C-1 or above B-8 (such samples should not
		// be written). Let's insert a sample there and check if re-loading the
		// file still works.
		GetSoundFile(sndFileContainer).m_nSamples++;
		GetSoundFile(sndFileContainer).Instruments[1]->Keyboard[110] = GetSoundFile(sndFileContainer).GetNumSamples();
		GetSoundFile(sndFileContainer).Patterns[1].GetpModCommand(30, 0)->note = NOTE_MIN;        // Should not be saved to file
		GetSoundFile(sndFileContainer).Patterns[1].GetpModCommand(31, 0)->note = NOTE_MIN + 108;  // Ditto

		#ifndef MODPLUG_NO_FILESAVE
			// Test file saving
			GetSoundFile(sndFileContainer).m_dwLastSavedWithVersion = Version::Current();
#if MPT_OS_DJGPP
			SaveXM(sndFileContainer, filenameBase + P_("sxm"));
#else
			SaveXM(sndFileContainer, filenameBase + P_("saved.xm"));
#endif
		#endif

		DestroySoundFileContainer(sndFileContainer);
	}

	// Reload the saved file and test if everything is still working correctly.
	#ifndef MODPLUG_NO_FILESAVE
	{
#if MPT_OS_DJGPP
		TSoundFileContainer sndFileContainer = CreateSoundFileContainer(filenameBase + P_("sxm"));
#else
		TSoundFileContainer sndFileContainer = CreateSoundFileContainer(filenameBase + P_("saved.xm"));
#endif

		TestLoadXMFile(GetSoundFile(sndFileContainer));

		DestroySoundFileContainer(sndFileContainer);

#if MPT_OS_DJGPP
		RemoveFile(filenameBase + P_("sxm"));
#else
		RemoveFile(filenameBase + P_("saved.xm"));
#endif
	}
	#endif

	// Test S3M file loading
	{
		TSoundFileContainer sndFileContainer = CreateSoundFileContainer(filenameBaseSrc + P_("s3m"));
		auto &sndFile = GetSoundFile(sndFileContainer);

		TestLoadS3MFile(sndFile, false);

		// Test GetLength code, in particular with subsongs
		sndFile.ChnSettings[1].dwFlags.reset(CHN_MUTE);
		
		VERIFY_EQUAL_EPS(sndFile.GetLength(eAdjustSamplePositions, GetLengthTarget(3, 1)).back().duration, 19.237, 0.01);
		VERIFY_EQUAL_NONCONT(sndFile.GetLength(eAdjustSamplePositions, GetLengthTarget(2, 0).StartPos(0, 1, 0)).back().targetReached, false);

		auto allSubSongs = sndFile.GetLength(eNoAdjust, GetLengthTarget(true));
		VERIFY_EQUAL_NONCONT(allSubSongs.size(), 3);
		double totalDuration = 0.0;
		for(const auto &subSong : allSubSongs)
		{
			totalDuration += subSong.duration;
		}
		VERIFY_EQUAL_EPS(totalDuration, 3674.38, 1.0);

		#ifndef MODPLUG_NO_FILESAVE
			// Test file saving
			sndFile.ChnSettings[1].dwFlags.set(CHN_MUTE);
			sndFile.Patterns[0].GetpModCommand(4, 0)->note = NOTE_MIN;        // Should not be saved to file
			sndFile.Patterns[0].GetpModCommand(5, 0)->note = NOTE_MIN + 108;  // Ditto
			sndFile.m_dwLastSavedWithVersion = Version::Current();
#if MPT_OS_DJGPP
			SaveS3M(sndFileContainer, filenameBase + P_("ss3"));
#else
			SaveS3M(sndFileContainer, filenameBase + P_("saved.s3m"));
#endif
		#endif

		DestroySoundFileContainer(sndFileContainer);
	}

	// Reload the saved file and test if everything is still working correctly.
	#ifndef MODPLUG_NO_FILESAVE
	{
#if MPT_OS_DJGPP
		TSoundFileContainer sndFileContainer = CreateSoundFileContainer(filenameBase + P_("ss3"));
#else
		TSoundFileContainer sndFileContainer = CreateSoundFileContainer(filenameBase + P_("saved.s3m"));
#endif

		TestLoadS3MFile(GetSoundFile(sndFileContainer), true);

		DestroySoundFileContainer(sndFileContainer);

#if MPT_OS_DJGPP
		RemoveFile(filenameBase + P_("ss3"));
#else
		RemoveFile(filenameBase + P_("saved.s3m"));
#endif
	}
	#endif

	// Test MOD file loading
	{
		TSoundFileContainer sndFileContainer = CreateSoundFileContainer(filenameBaseSrc + P_("mod"));
		auto &sndFile = GetSoundFile(sndFileContainer);

		TestLoadMODFile(sndFile);

#ifndef MODPLUG_NO_FILESAVE
		// Test file saving
#if MPT_OS_DJGPP
		SaveMOD(sndFileContainer, filenameBase + P_("smo"));
#else
		SaveMOD(sndFileContainer, filenameBase + P_("saved.mod"));
#endif
#endif

		DestroySoundFileContainer(sndFileContainer);
	}

	// Reload the saved file and test if everything is still working correctly.
#ifndef MODPLUG_NO_FILESAVE
	{
#if MPT_OS_DJGPP
		TSoundFileContainer sndFileContainer = CreateSoundFileContainer(filenameBase + P_("smo"));
#else
		TSoundFileContainer sndFileContainer = CreateSoundFileContainer(filenameBase + P_("saved.mod"));
#endif
		TestLoadMODFile(GetSoundFile(sndFileContainer));
		DestroySoundFileContainer(sndFileContainer);
#if MPT_OS_DJGPP
		RemoveFile(filenameBase + P_("smo"));
#else
		RemoveFile(filenameBase + P_("saved.mod"));
#endif
	}
#endif

	// General file I/O tests
	{
		std::ostringstream f;
		size_t bytesWritten;
		mpt::IO::WriteVarInt(f, uint16(0), &bytesWritten);		VERIFY_EQUAL_NONCONT(bytesWritten, 1);
		mpt::IO::WriteVarInt(f, uint16(127), &bytesWritten);	VERIFY_EQUAL_NONCONT(bytesWritten, 1);
		mpt::IO::WriteVarInt(f, uint16(128), &bytesWritten);	VERIFY_EQUAL_NONCONT(bytesWritten, 2);
		mpt::IO::WriteVarInt(f, uint16(16383), &bytesWritten);	VERIFY_EQUAL_NONCONT(bytesWritten, 2);
		mpt::IO::WriteVarInt(f, uint16(16384), &bytesWritten);	VERIFY_EQUAL_NONCONT(bytesWritten, 3);
		mpt::IO::WriteVarInt(f, uint16(65535), &bytesWritten);	VERIFY_EQUAL_NONCONT(bytesWritten, 3);
		mpt::IO::WriteVarInt(f, uint64(0xFFFFFFFFFFFFFFFFull), &bytesWritten);	VERIFY_EQUAL_NONCONT(bytesWritten, 10);
		std::string data = f.str();
		FileReader file(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(data)));
		uint64 v;
		file.ReadVarInt(v); VERIFY_EQUAL_NONCONT(v, 0);
		file.ReadVarInt(v); VERIFY_EQUAL_NONCONT(v, 127);
		file.ReadVarInt(v); VERIFY_EQUAL_NONCONT(v, 128);
		file.ReadVarInt(v); VERIFY_EQUAL_NONCONT(v, 16383);
		file.ReadVarInt(v); VERIFY_EQUAL_NONCONT(v, 16384);
		file.ReadVarInt(v); VERIFY_EQUAL_NONCONT(v, 65535);
		file.ReadVarInt(v); VERIFY_EQUAL_NONCONT(v, 0xFFFFFFFFFFFFFFFFull);
	}

#ifdef MODPLUG_TRACKER
	TrackerSettings::Instance().MiscSaveChannelMuteStatus = saveMutedChannels;
#endif
}


// Test various editing features
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestEditing()
{
#ifdef MODPLUG_TRACKER
	auto modDoc = static_cast<CModDoc *>(theApp.GetModDocTemplate()->CreateNewDocument());
	auto &sndFile = modDoc->GetSoundFile();
	sndFile.Create(MOD_TYPE_MPT, 4, modDoc);

	// Rearrange channels
	sndFile.Patterns.ResizeArray(2);
	sndFile.Patterns.Insert(0, 32);
	sndFile.Patterns.Insert(1, 48);
	sndFile.Patterns[1].SetName("Pattern");
	sndFile.Patterns[1].SetSignature(2, 4);
	TempoSwing swing;
	swing.resize(2);
	sndFile.Patterns[1].SetTempoSwing(swing);
	sndFile.Patterns[1].GetpModCommand(37, 0)->instr = 1;
	sndFile.Patterns[1].GetpModCommand(37, 1)->instr = 2;
	sndFile.Patterns[1].GetpModCommand(37, 2)->instr = 3;
	sndFile.Patterns[1].GetpModCommand(37, 3)->instr = 4;
	modDoc->ReArrangeChannels({ 3, 2, CHANNELINDEX_INVALID, 0 });
	modDoc->ReArrangeChannels({ 0, 1, 1, CHANNELINDEX_INVALID, 3 });
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetName(), "Pattern");
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetRowsPerBeat(), 2);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetRowsPerMeasure(), 4);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetTempoSwing(), swing);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(37, 0)->instr, 4);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(37, 1)->instr, 3);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(37, 2)->instr, 3);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(37, 3)->instr, 0);
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(37, 4)->instr, 1);

	// Rearrange samples
	sndFile.m_nSamples = 2;
	sndFile.GetSample(1).filename = "1";
	sndFile.m_szNames[1] = "1";
	sndFile.GetSample(2).filename = "2";
	sndFile.m_szNames[2] = "2";
	sndFile.GetSample(2).nLength = 16;
	sndFile.GetSample(2).AllocateSample();
	modDoc->ReArrangeSamples({ 2, SAMPLEINDEX_INVALID, 1 });
	VERIFY_EQUAL_NONCONT(sndFile.GetSample(1).HasSampleData(), true);
	VERIFY_EQUAL_NONCONT(sndFile.GetSample(1).filename, "2");
	VERIFY_EQUAL_NONCONT(sndFile.m_szNames[1], "2");
	VERIFY_EQUAL_NONCONT(sndFile.GetSample(2).filename, "");
	VERIFY_EQUAL_NONCONT(sndFile.m_szNames[2], "");
	VERIFY_EQUAL_NONCONT(sndFile.GetSample(3).filename, "1");
	VERIFY_EQUAL_NONCONT(sndFile.m_szNames[3], "1");
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(37, 4)->instr, 3);

	// Convert / rearrange instruments
	modDoc->ConvertSamplesToInstruments();
	modDoc->ReArrangeInstruments({ INSTRUMENTINDEX_INVALID, 2, 1, 3 });
	VERIFY_EQUAL_NONCONT(sndFile.Instruments[1]->name, "");
	VERIFY_EQUAL_NONCONT(sndFile.Instruments[2]->name, "");
	VERIFY_EQUAL_NONCONT(sndFile.Instruments[3]->name, "2");
	VERIFY_EQUAL_NONCONT(sndFile.Instruments[4]->name, "1");
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(37, 4)->instr, 4);
	modDoc->ConvertInstrumentsToSamples();
	VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(37, 4)->instr, 3);

	modDoc->SetModified();
	VERIFY_EQUAL_NONCONT(modDoc->IsModified(), true);
	VERIFY_EQUAL_NONCONT(modDoc->ModifiedSinceLastAutosave(), true);
	VERIFY_EQUAL_NONCONT(modDoc->ModifiedSinceLastAutosave(), false);

	sndFile.Destroy();
	modDoc->OnCloseDocument();
#endif
}


static void RunITCompressionTest(const std::vector<int8> &sampleData, FlagSet<ChannelFlags> smpFormat, bool it215)
{

	ModSample smp;
	smp.uFlags = smpFormat;
	smp.pData.pSample = const_cast<int8 *>(sampleData.data());
	smp.nLength = mpt::saturate_cast<SmpLength>(sampleData.size() / smp.GetBytesPerSample());

	std::string data;

	{
		std::ostringstream f;
		ITCompression compression(smp, it215, &f);
		data = f.str();
	}

	{
		FileReader file(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(data)));

		std::vector<int8> sampleDataNew(sampleData.size(), 0);
		smp.pData.pSample = sampleDataNew.data();

		ITDecompression decompression(file, smp, it215);
		VERIFY_EQUAL_NONCONT(memcmp(sampleData.data(), sampleDataNew.data(), sampleData.size()), 0);
	}
}


MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestITCompression()
{
	// Test loading / saving of IT-compressed samples
	constexpr int sampleDataSize = 131072;
	std::vector<int8> sampleData(sampleDataSize, 0);
	for(int i = 0; i < sampleDataSize; i++)
	{
		sampleData[i] = mpt::random<int8>(*s_PRNG);
	}
	// Fade in the first half of the sample so that we test lower bit widths as well
	for(int i = 0; i < sampleDataSize / 2; i++)
	{
		sampleData[i] = static_cast<int8>(Util::muldivr(sampleData[i], i, sampleDataSize / 2));
	}
	// Add a few irregularities to the signal to provoke temporary switches to higher bit width
	for(int i = 99; i < sampleDataSize / 2; i += 100)
	{
		sampleData[i] ^= 99;
	}

	// Run each compression test with IT215 compression and without.
	for(int i = 0; i < 2; i++)
	{
		RunITCompressionTest(sampleData, ChannelFlags(0), i == 0);
		RunITCompressionTest(sampleData, CHN_16BIT, i == 0);
		RunITCompressionTest(sampleData, CHN_STEREO, i == 0);
		RunITCompressionTest(sampleData, CHN_16BIT | CHN_STEREO, i == 0);
	}
}



#if 0

static bool RatioEqual(CTuningBase::RATIOTYPE a, CTuningBase::RATIOTYPE b)
{
	if(a == CTuningBase::RATIOTYPE(0) && b == CTuningBase::RATIOTYPE(0))
	{
		return true;
	}
	if(a == CTuningBase::RATIOTYPE(0) || b == CTuningBase::RATIOTYPE(0))
	{
		return false;
	}
	return (std::fabs(CTuningBase::RATIOTYPE(1) - (a/b)) < CTuningBase::RATIOTYPE(0.0001));
}


static void CheckEqualTuningCollections(const CTuningCollection &a, const CTuningCollection &b)
{
	VERIFY_EQUAL(a.GetName(), b.GetName());
	VERIFY_EQUAL(a.GetNumTunings(), b.GetNumTunings());
	for(std::size_t tuning = 0; tuning < std::min(a.GetNumTunings(), b.GetNumTunings()); ++tuning)
	{
		VERIFY_EQUAL(a.GetTuning(tuning).GetName(), b.GetTuning(tuning).GetName());
		VERIFY_EQUAL(a.GetTuning(tuning).GetType(), b.GetTuning(tuning).GetType());
		VERIFY_EQUAL(a.GetTuning(tuning).GetGroupSize(), b.GetTuning(tuning).GetGroupSize());
		VERIFY_EQUAL(a.GetTuning(tuning).GetFineStepCount(), b.GetTuning(tuning).GetFineStepCount());
		VERIFY_EQUAL(RatioEqual(a.GetTuning(tuning).GetGroupRatio(), b.GetTuning(tuning).GetGroupRatio()), true);
		VERIFY_EQUAL(a.GetTuning(tuning).GetValidityRange(), b.GetTuning(tuning).GetValidityRange());
		for(ModCommand::NOTE note = NOTE_MIN; note <= NOTE_MAX; ++note)
		{
			VERIFY_EQUAL(a.GetTuning(tuning).GetNoteName(note - NOTE_MIDDLEC), b.GetTuning(tuning).GetNoteName(note - NOTE_MIDDLEC));
			VERIFY_EQUAL(RatioEqual(a.GetTuning(tuning).GetRatio(note - NOTE_MIDDLEC), b.GetTuning(tuning).GetRatio(note - NOTE_MIDDLEC)), true);
		}
	}
}

#endif



static double Rand01()
{
	return mpt::random(*s_PRNG, 0.0, 1.0);
}

template <class T>
T Rand(const T min, const T max)
{
	return mpt::saturate_round<T>(min + Rand01() * (max - min));
}

static void GenerateCommands(CPattern& pat, const double dProbPcs, const double dProbPc)
{
	const double dPcxProb = dProbPcs + dProbPc;
	for(auto &m : pat)
	{
		const double rand = Rand01();
		if(rand < dPcxProb)
		{
			if(rand < dProbPcs)
				m.note = NOTE_PCS;
			else
				m.note = NOTE_PC;

			m.instr = Rand<ModCommand::INSTR>(0, MAX_MIXPLUGINS);
			m.SetValueVolCol(Rand<uint16>(0, ModCommand::maxColumnValue));
			m.SetValueEffectCol(Rand<uint16>(0, ModCommand::maxColumnValue));
		}
		else
			m.Clear();
	}
}


// Test PC note serialization
MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestPCnoteSerialization()
{
	FileReader file;
	mpt::heap_value<CSoundFile> pSndFile;
	CSoundFile &sndFile = *pSndFile;
	sndFile.Create(MOD_TYPE_MPT, ModSpecs::mptm.channelsMax);

	sndFile.Patterns.Insert(0, ModSpecs::mptm.patternRowsMin);
	sndFile.Patterns.Insert(1, 64);
	GenerateCommands(sndFile.Patterns[1], 0.3, 0.3);
	sndFile.Patterns.Insert(2, ModSpecs::mptm.patternRowsMax);
	GenerateCommands(sndFile.Patterns[2], 0.5, 0.5);

	// Copy pattern data for comparison.
	CPatternContainer patterns{ sndFile.Patterns };

	std::stringstream mem;
	WriteModPatterns(mem, sndFile.Patterns);

	VERIFY_EQUAL_NONCONT( mem.good(), true );

	// Clear patterns.
	sndFile.Patterns[0].ClearCommands();
	sndFile.Patterns[1].ClearCommands();
	sndFile.Patterns[2].ClearCommands();

	// Read data back.
	ReadModPatterns(mem, sndFile.Patterns);

	// Compare.
	VERIFY_EQUAL_NONCONT( sndFile.Patterns[0].GetNumRows(), ModSpecs::mptm.patternRowsMin);
	VERIFY_EQUAL_NONCONT( sndFile.Patterns[1].GetNumRows(), 64);
	VERIFY_EQUAL_NONCONT( sndFile.Patterns[2].GetNumRows(), ModSpecs::mptm.patternRowsMax);
	for(int i = 0; i < 3; i++)
	{
		VERIFY_EQUAL(sndFile.Patterns[i], patterns[i]);
	}
}


static inline std::size_t strnlen(const char *str, std::size_t n)
{
#if MPT_COMPILER_MSVC
	return ::strnlen(str, n);
#else
	if(n >= std::numeric_limits<std::size_t>::max())
	{
		return std::strlen(str);
	}
	for(std::size_t i = 0; i < n; ++i)
	{
		if(str[i] == '\0')
		{
			return i;
		}
	}
	return n;
#endif
}


// Test String I/O functionality

MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestStringIO1()
{

#if MPT_COMPILER_MSVC
#pragma warning(push)
#pragma warning(disable:6054) // String '' might not be zero-terminated.
#endif // MPT_COMPILER_MSVC

	char src0[4] = { '\0', 'X', ' ', 'X' };		// Weird empty buffer
	char src1[4] = { 'X', ' ', '\0', 'X' };		// Weird buffer (hello Impulse Tracker)
	char src2[4] = { 'X', 'Y', 'Z', ' ' };		// Full buffer, last character space
	char src3[4] = { 'X', 'Y', 'Z', '!' };		// Full buffer, last character non-space
	char src4[4] = { 'x', 'y', '\t', '\n' };	// Full buffer containing non-space whitespace
	char dst1[6];	// Destination buffer, larger than source buffer
	char dst2[3];	// Destination buffer, smaller than source buffer

#define ReadTest(mode, dst, src, expectedResult) \
	std::memset(dst, 0x7f, sizeof(dst)); \
	mpt::String::WriteAutoBuf(dst) = mpt::String::ReadBuf(mpt::String:: mode , src); \
	VERIFY_EQUAL_NONCONT(strncmp(dst, expectedResult, std::size(dst)), 0); /* Ensure that the strings are identical */ \
	for(size_t i = strlen(dst); i < std::size(dst); i++) \
		VERIFY_EQUAL_NONCONT(dst[i], '\0'); /* Ensure that rest of the buffer is completely nulled */ \
	/**/

#define WriteTest(mode, dst, src, expectedResult) \
	std::memset(dst, 0x7f, sizeof(dst)); \
	mpt::String::WriteBuf(mpt::String:: mode , dst) = mpt::String::ReadAutoBuf(src); \
	VERIFY_EQUAL_NONCONT(strncmp(dst, expectedResult, std::size(dst)), 0);  /* Ensure that the strings are identical */ \
	for(size_t i = Test::strnlen(dst, std::size(dst)); i < std::size(dst); i++) \
		VERIFY_EQUAL_NONCONT(dst[i], '\0'); /* Ensure that rest of the buffer is completely nulled */ \
	/**/

	// Check reading of null-terminated string into larger buffer
	ReadTest(nullTerminated, dst1, src0, "");
	ReadTest(nullTerminated, dst1, src1, "X ");
	ReadTest(nullTerminated, dst1, src2, "XYZ");
	ReadTest(nullTerminated, dst1, src3, "XYZ");
	ReadTest(nullTerminated, dst1, src4, "xy\t");

	// Check reading of string that should be null-terminated, but is maybe too long to still hold the null character.
	ReadTest(maybeNullTerminated, dst1, src0, "");
	ReadTest(maybeNullTerminated, dst1, src1, "X ");
	ReadTest(maybeNullTerminated, dst1, src2, "XYZ ");
	ReadTest(maybeNullTerminated, dst1, src3, "XYZ!");
	ReadTest(maybeNullTerminated, dst1, src4, "xy\t\n");

	// Check reading of space-padded strings with ignored last character
	ReadTest(spacePaddedNull, dst1, src0, " X");
	ReadTest(spacePaddedNull, dst1, src1, "X");
	ReadTest(spacePaddedNull, dst1, src2, "XYZ");
	ReadTest(spacePaddedNull, dst1, src3, "XYZ");
	ReadTest(spacePaddedNull, dst1, src4, "xy\t");

	// Check reading of space-padded strings
	ReadTest(spacePadded, dst1, src0, " X X");
	ReadTest(spacePadded, dst1, src1, "X  X");
	ReadTest(spacePadded, dst1, src2, "XYZ");
	ReadTest(spacePadded, dst1, src3, "XYZ!");
	ReadTest(spacePadded, dst1, src4, "xy\t\n");

	///////////////////////////////

	// Check reading of null-terminated string into smaller buffer
	ReadTest(nullTerminated, dst2, src0, "");
	ReadTest(nullTerminated, dst2, src1, "X ");
	ReadTest(nullTerminated, dst2, src2, "XY");
	ReadTest(nullTerminated, dst2, src3, "XY");
	ReadTest(nullTerminated, dst2, src4, "xy");

	// Check reading of string that should be null-terminated, but is maybe too long to still hold the null character.
	ReadTest(maybeNullTerminated, dst2, src0, "");
	ReadTest(maybeNullTerminated, dst2, src1, "X ");
	ReadTest(maybeNullTerminated, dst2, src2, "XY");
	ReadTest(maybeNullTerminated, dst2, src3, "XY");
	ReadTest(maybeNullTerminated, dst2, src4, "xy");

	// Check reading of space-padded strings with ignored last character
	ReadTest(spacePaddedNull, dst2, src0, " X");
	ReadTest(spacePaddedNull, dst2, src1, "X");
	ReadTest(spacePaddedNull, dst2, src2, "XY");
	ReadTest(spacePaddedNull, dst2, src3, "XY");
	ReadTest(spacePaddedNull, dst2, src4, "xy");

	// Check reading of space-padded strings
	ReadTest(spacePadded, dst2, src0, " X");
	ReadTest(spacePadded, dst2, src1, "X ");
	ReadTest(spacePadded, dst2, src2, "XY");
	ReadTest(spacePadded, dst2, src3, "XY");
	ReadTest(spacePadded, dst2, src4, "xy");

	///////////////////////////////

	// Check writing of null-terminated string into larger buffer
	WriteTest(nullTerminated, dst1, src0, "");
	WriteTest(nullTerminated, dst1, src1, "X ");
	WriteTest(nullTerminated, dst1, src2, "XYZ ");
	WriteTest(nullTerminated, dst1, src3, "XYZ!");

	// Check writing of string that should be null-terminated, but is maybe too long to still hold the null character.
	WriteTest(maybeNullTerminated, dst1, src0, "");
	WriteTest(maybeNullTerminated, dst1, src1, "X ");
	WriteTest(maybeNullTerminated, dst1, src2, "XYZ ");
	WriteTest(maybeNullTerminated, dst1, src3, "XYZ!");

	// Check writing of space-padded strings with last character set to null
	WriteTest(spacePaddedNull, dst1, src0, "     ");
	WriteTest(spacePaddedNull, dst1, src1, "X    ");
	WriteTest(spacePaddedNull, dst1, src2, "XYZ  ");
	WriteTest(spacePaddedNull, dst1, src3, "XYZ! ");

	// Check writing of space-padded strings
	WriteTest(spacePadded, dst1, src0, "      ");
	WriteTest(spacePadded, dst1, src1, "X     ");
	WriteTest(spacePadded, dst1, src2, "XYZ   ");
	WriteTest(spacePadded, dst1, src3, "XYZ!  ");

	///////////////////////////////

	// Check writing of null-terminated string into smaller buffer
	WriteTest(nullTerminated, dst2, src0, "");
	WriteTest(nullTerminated, dst2, src1, "X ");
	WriteTest(nullTerminated, dst2, src2, "XY");
	WriteTest(nullTerminated, dst2, src3, "XY");

	// Check writing of string that should be null-terminated, but is maybe too long to still hold the null character.
	WriteTest(maybeNullTerminated, dst2, src0, "");
	WriteTest(maybeNullTerminated, dst2, src1, "X ");
	WriteTest(maybeNullTerminated, dst2, src2, "XYZ");
	WriteTest(maybeNullTerminated, dst2, src3, "XYZ");

	// Check writing of space-padded strings with last character set to null
	WriteTest(spacePaddedNull, dst2, src0, "  ");
	WriteTest(spacePaddedNull, dst2, src1, "X ");
	WriteTest(spacePaddedNull, dst2, src2, "XY");
	WriteTest(spacePaddedNull, dst2, src3, "XY");

	// Check writing of space-padded strings
	WriteTest(spacePadded, dst2, src0, "   ");
	WriteTest(spacePadded, dst2, src1, "X  ");
	WriteTest(spacePadded, dst2, src2, "XYZ");
	WriteTest(spacePadded, dst2, src3, "XYZ");

#undef ReadTest
#undef WriteTest

#if MPT_COMPILER_MSVC
#pragma warning(push)
#endif // MPT_COMPILER_MSVC

	{

		std::string dststring;
		std::string src0string = std::string(src0, std::size(src0));
		std::string src1string = std::string(src1, std::size(src1));
		std::string src2string = std::string(src2, std::size(src2));
		std::string src3string = std::string(src3, std::size(src3));

#define ReadTest(mode, dst, src, expectedResult) \
	dst = mpt::String::ReadBuf(mpt::String:: mode , src); \
	VERIFY_EQUAL_NONCONT(dst, expectedResult); /* Ensure that the strings are identical */ \
	/**/

#define WriteTest(mode, dst, src, expectedResult) \
	std::memset(dst, 0x7f, sizeof(dst)); \
	mpt::String::WriteBuf(mpt::String:: mode , dst) = src; \
	VERIFY_EQUAL_NONCONT(strncmp(dst, expectedResult, std::size(dst)), 0);  /* Ensure that the strings are identical */ \
	for(size_t i = Test::strnlen(dst, std::size(dst)); i < std::size(dst); i++) \
		VERIFY_EQUAL_NONCONT(dst[i], '\0'); /* Ensure that rest of the buffer is completely nulled */ \
	/**/

		// Check reading of null-terminated string into std::string
		ReadTest(nullTerminated, dststring, src0, "");
		ReadTest(nullTerminated, dststring, src1, "X ");
		ReadTest(nullTerminated, dststring, src2, "XYZ");
		ReadTest(nullTerminated, dststring, src3, "XYZ");
		ReadTest(nullTerminated, dststring, src4, "xy\t");

		// Check reading of string that should be null-terminated, but is maybe too long to still hold the null character.
		ReadTest(maybeNullTerminated, dststring, src0, "");
		ReadTest(maybeNullTerminated, dststring, src1, "X ");
		ReadTest(maybeNullTerminated, dststring, src2, "XYZ ");
		ReadTest(maybeNullTerminated, dststring, src3, "XYZ!");
		ReadTest(maybeNullTerminated, dststring, src4, "xy\t\n");

		// Check reading of space-padded strings with ignored last character
		ReadTest(spacePaddedNull, dststring, src0, " X");
		ReadTest(spacePaddedNull, dststring, src1, "X");
		ReadTest(spacePaddedNull, dststring, src2, "XYZ");
		ReadTest(spacePaddedNull, dststring, src3, "XYZ");
		ReadTest(spacePaddedNull, dststring, src4, "xy\t");

		// Check reading of space-padded strings
		ReadTest(spacePadded, dststring, src0, " X X");
		ReadTest(spacePadded, dststring, src1, "X  X");
		ReadTest(spacePadded, dststring, src2, "XYZ");
		ReadTest(spacePadded, dststring, src3, "XYZ!");
		ReadTest(spacePadded, dststring, src4, "xy\t\n");

		///////////////////////////////

		// Check writing of null-terminated string into larger buffer
		WriteTest(nullTerminated, dst1, src0string, "");
		WriteTest(nullTerminated, dst1, src1string, "X ");
		WriteTest(nullTerminated, dst1, src2string, "XYZ ");
		WriteTest(nullTerminated, dst1, src3string, "XYZ!");

		// Check writing of string that should be null-terminated, but is maybe too long to still hold the null character.
		WriteTest(maybeNullTerminated, dst1, src0string, "");
		WriteTest(maybeNullTerminated, dst1, src1string, "X ");
		WriteTest(maybeNullTerminated, dst1, src2string, "XYZ ");
		WriteTest(maybeNullTerminated, dst1, src3string, "XYZ!");

		// Check writing of space-padded strings with last character set to null
		WriteTest(spacePaddedNull, dst1, src0string, "     ");
		WriteTest(spacePaddedNull, dst1, src1string, "X    ");
		WriteTest(spacePaddedNull, dst1, src2string, "XYZ  ");
		WriteTest(spacePaddedNull, dst1, src3string, "XYZ! ");

		// Check writing of space-padded strings
		WriteTest(spacePadded, dst1, src0string, "      ");
		WriteTest(spacePadded, dst1, src1string, "X     ");
		WriteTest(spacePadded, dst1, src2string, "XYZ   ");
		WriteTest(spacePadded, dst1, src3string, "XYZ!  ");

		///////////////////////////////

		// Check writing of null-terminated string into smaller buffer
		WriteTest(nullTerminated, dst2, src0string, "");
		WriteTest(nullTerminated, dst2, src1string, "X ");
		WriteTest(nullTerminated, dst2, src2string, "XY");
		WriteTest(nullTerminated, dst2, src3string, "XY");

		// Check writing of string that should be null-terminated, but is maybe too long to still hold the null character.
		WriteTest(maybeNullTerminated, dst2, src0string, "");
		WriteTest(maybeNullTerminated, dst2, src1string, "X ");
		WriteTest(maybeNullTerminated, dst2, src2string, "XYZ");
		WriteTest(maybeNullTerminated, dst2, src3string, "XYZ");

		// Check writing of space-padded strings with last character set to null
		WriteTest(spacePaddedNull, dst2, src0string, "  ");
		WriteTest(spacePaddedNull, dst2, src1string, "X ");
		WriteTest(spacePaddedNull, dst2, src2string, "XY");
		WriteTest(spacePaddedNull, dst2, src3string, "XY");

		// Check writing of space-padded strings
		WriteTest(spacePadded, dst2, src0string, "   ");
		WriteTest(spacePadded, dst2, src1string, "X  ");
		WriteTest(spacePadded, dst2, src2string, "XYZ");
		WriteTest(spacePadded, dst2, src3string, "XYZ");

		///////////////////////////////

#undef ReadTest
#undef WriteTest

	}
}

MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestStringIO2()
{
	{
	
		char s0[4] = {'\0', 'X', ' ', 'X' };
		char s2[4] = { 'X', ' ','\0', 'X' };
		char s4[4] = { 'X', 'Y', 'Z', ' ' };
	
		char d2[2] = {'\0','\0'};
		char d3[3] = {'\0','\0','\0'};
		char d4[4] = {'\0','\0','\0','\0'};
		char d5[5] = {'\0','\0','\0','\0','\0'};
	
		#define CopyTest(dst, src, expectedResult) \
			std::memset(dst, 0x7f, sizeof(dst)); \
			mpt::String::WriteAutoBuf(dst) = mpt::String::ReadAutoBuf(src); \
			VERIFY_EQUAL_NONCONT(strncmp(dst, expectedResult, std::size(dst)), 0); /* Ensure that the strings are identical */ \
			for(size_t i = strlen(dst); i < std::size(dst); i++) \
				VERIFY_EQUAL_NONCONT(dst[i], '\0'); /* Ensure that rest of the buffer is completely nulled */ \
			/**/

		CopyTest(d2, s0, "");
		CopyTest(d2, s2, "X");
		CopyTest(d2, s4, "X");
		CopyTest(d3, s0, "");
		CopyTest(d3, s2, "X ");
		CopyTest(d3, s4, "XY");
		CopyTest(d4, s0, "");
		CopyTest(d4, s2, "X ");
		CopyTest(d4, s4, "XYZ");
		CopyTest(d5, s0, "");
		CopyTest(d5, s2, "X ");
		CopyTest(d5, s4, "XYZ ");

		#undef CopyTest

		#define CopyTestN(dst, src, len, expectedResult) \
			std::memset(dst, 0x7f, sizeof(dst)); \
			mpt::String::WriteAutoBuf(dst) = mpt::String::ReadAutoBuf(src, std::min(std::size(src), static_cast<std::size_t>(len))); \
			VERIFY_EQUAL_NONCONT(strncmp(dst, expectedResult, std::size(dst)), 0); /* Ensure that the strings are identical */ \
			for(size_t i = strlen(dst); i < std::size(dst); i++) \
				VERIFY_EQUAL_NONCONT(dst[i], '\0'); /* Ensure that rest of the buffer is completely nulled */ \
			/**/

		CopyTestN(d2, s0, 1, "");
		CopyTestN(d2, s2, 1, "X");
		CopyTestN(d2, s4, 1, "X");
		CopyTestN(d3, s0, 1, "");
		CopyTestN(d3, s2, 1, "X");
		CopyTestN(d3, s4, 1, "X");
		CopyTestN(d4, s0, 1, "");
		CopyTestN(d4, s2, 1, "X");
		CopyTestN(d4, s4, 1, "X");
		CopyTestN(d5, s0, 1, "");
		CopyTestN(d5, s2, 1, "X");
		CopyTestN(d5, s4, 1, "X");

		CopyTestN(d2, s0, 2, "");
		CopyTestN(d2, s2, 2, "X");
		CopyTestN(d2, s4, 2, "X");
		CopyTestN(d3, s0, 2, "");
		CopyTestN(d3, s2, 2, "X ");
		CopyTestN(d3, s4, 2, "XY");
		CopyTestN(d4, s0, 2, "");
		CopyTestN(d4, s2, 2, "X ");
		CopyTestN(d4, s4, 2, "XY");
		CopyTestN(d5, s0, 2, "");
		CopyTestN(d5, s2, 2, "X ");
		CopyTestN(d5, s4, 2, "XY");

		CopyTestN(d2, s0, 3, "");
		CopyTestN(d2, s2, 3, "X");
		CopyTestN(d2, s4, 3, "X");
		CopyTestN(d3, s0, 3, "");
		CopyTestN(d3, s2, 3, "X ");
		CopyTestN(d3, s4, 3, "XY");
		CopyTestN(d4, s0, 3, "");
		CopyTestN(d4, s2, 3, "X ");
		CopyTestN(d4, s4, 3, "XYZ");
		CopyTestN(d5, s0, 3, "");
		CopyTestN(d5, s2, 3, "X ");
		CopyTestN(d5, s4, 3, "XYZ");

		CopyTestN(d2, s0, 4, "");
		CopyTestN(d2, s2, 4, "X");
		CopyTestN(d2, s4, 4, "X");
		CopyTestN(d3, s0, 4, "");
		CopyTestN(d3, s2, 4, "X ");
		CopyTestN(d3, s4, 4, "XY");
		CopyTestN(d4, s0, 4, "");
		CopyTestN(d4, s2, 4, "X ");
		CopyTestN(d4, s4, 4, "XYZ");
		CopyTestN(d5, s0, 4, "");
		CopyTestN(d5, s2, 4, "X ");
		CopyTestN(d5, s4, 4, "XYZ ");

		CopyTestN(d2, s0, 5, "");
		CopyTestN(d2, s2, 5, "X");
		CopyTestN(d2, s4, 5, "X");
		CopyTestN(d3, s0, 5, "");
		CopyTestN(d3, s2, 5, "X ");
		CopyTestN(d3, s4, 5, "XY");
		CopyTestN(d4, s0, 5, "");
		CopyTestN(d4, s2, 5, "X ");
		CopyTestN(d4, s4, 5, "XYZ");
		CopyTestN(d5, s0, 5, "");
		CopyTestN(d5, s2, 5, "X ");
		CopyTestN(d5, s4, 5, "XYZ ");

		#undef CopyTest

	}
}

MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestStringIO()
{
	TestStringIO1();
	TestStringIO2();
}

MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestSampleConversion()
{
	// Signed 8-Bit Integer PCM
	// Unsigned 8-Bit Integer PCM
	// Delta 8-Bit Integer PCM
	{
		std::vector<std::byte> source8(256);
		for(std::size_t i = 0; i < 256; i++)
		{
			source8[i] = mpt::byte_cast<std::byte>(static_cast<uint8>(i));
		}

		std::vector<int8> signed8(256);
		std::vector<int8> unsigned8(256);
		std::vector<int8> delta8(256);
		int8 delta = 0;
		DecodeSample<SC::DecodeInt8>(signed8.data(), 256, 1, source8.data(), 256, 1);
		DecodeSample<SC::DecodeUint8>(unsigned8.data(), 256, 1, source8.data(), 256, 1);
		DecodeSample<SC::DecodeInt8Delta>(delta8.data(), 256, 1, source8.data(), 256, 1);

		for(std::size_t i = 0; i < 256; i++)
		{
			delta += static_cast<int8>(i);
			VERIFY_EQUAL_QUIET_NONCONT(signed8[i], static_cast<int8>(i));
			VERIFY_EQUAL_QUIET_NONCONT(unsigned8[i], static_cast<int8>(static_cast<int>(i) - 0x80));
			VERIFY_EQUAL_QUIET_NONCONT(delta8[i], static_cast<int8>(delta));
		}
	}

	// Signed 16-Bit Integer PCM
	// Unsigned 16-Bit Integer PCM
	// Delta 16-Bit Integer PCM
	{
		// Little Endian

		std::vector<std::byte> source16(65536 * 2);
		for(std::size_t i = 0; i < 65536; i++)
		{
			source16[i * 2 + 0] = mpt::byte_cast<std::byte>(static_cast<uint8>(i & 0xFF));
			source16[i * 2 + 1] = mpt::byte_cast<std::byte>(static_cast<uint8>(i >> 8));
		}

		std::vector<int16> signed16(65536);
		std::vector<int16> unsigned16(65536);
		std::vector<int16> delta16(65536);
		int16 delta = 0;
		DecodeSample<SC::DecodeInt16<0, littleEndian16> >(signed16.data(), 65536, 1, source16.data(), 65536 * 2, 1);
		DecodeSample<SC::DecodeInt16<0x8000u, littleEndian16> >(unsigned16.data(), 65536, 1, source16.data(), 65536 * 2, 1);
		DecodeSample<SC::DecodeInt16Delta<littleEndian16> >(delta16.data(), 65536, 1, source16.data(), 65536 * 2, 1);

		for(std::size_t i = 0; i < 65536; i++)
		{
			delta += static_cast<int16>(i);
			VERIFY_EQUAL_QUIET_NONCONT(signed16[i], static_cast<int16>(i));
			VERIFY_EQUAL_QUIET_NONCONT(unsigned16[i], static_cast<int16>(static_cast<int>(i) - 0x8000));
			VERIFY_EQUAL_QUIET_NONCONT(delta16[i], static_cast<int16>(delta));
		}

		// Big Endian

		for(std::size_t i = 0; i < 65536; i++)
		{
			source16[i * 2 + 0] = mpt::byte_cast<std::byte>(static_cast<uint8>(i >> 8));
			source16[i * 2 + 1] = mpt::byte_cast<std::byte>(static_cast<uint8>(i & 0xFF));
		}

		DecodeSample<SC::DecodeInt16<0, bigEndian16> >(signed16.data(), 65536, 1, source16.data(), 65536 * 2, 1);
		DecodeSample<SC::DecodeInt16<0x8000u, bigEndian16> >(unsigned16.data(), 65536, 1, source16.data(), 65536 * 2, 1);
		DecodeSample<SC::DecodeInt16Delta<bigEndian16> >(delta16.data(), 65536, 1, source16.data(), 65536 * 2, 1);

		delta = 0;
		for(size_t i = 0; i < 65536; i++)
		{
			delta += static_cast<int16>(i);
			VERIFY_EQUAL_QUIET_NONCONT(signed16[i], static_cast<int16>(i));
			VERIFY_EQUAL_QUIET_NONCONT(unsigned16[i], static_cast<int16>(static_cast<int>(i) - 0x8000));
			VERIFY_EQUAL_QUIET_NONCONT(delta16[i], static_cast<int16>(delta));
		}

	}

	// Signed 24-Bit Integer PCM
	{
		std::vector<std::byte> source24(65536 * 3);
		for(std::size_t i = 0; i < 65536; i++)
		{
			source24[i * 3 + 0] = mpt::byte_cast<std::byte>(static_cast<uint8>(0));
			source24[i * 3 + 1] = mpt::byte_cast<std::byte>(static_cast<uint8>(i & 0xFF));
			source24[i * 3 + 2] = mpt::byte_cast<std::byte>(static_cast<uint8>(i >> 8));
		}

		std::vector<int16> truncated16(65536);
		std::vector<int16> sampleBuf(65536);
		ModSample sample;
		sample.Initialize();
		sample.nLength = 65536;
		sample.uFlags.set(CHN_16BIT);
		sample.pData.pSample = sampleBuf.data();
		DecodeAndNormalizeSample<SC::NormalizationChain<SC::Convert<int16, int32>, SC::DecodeInt24<0, littleEndian24> > >(sample, source24.data(), 3*65536);
		DecodeSample<SC::DecodeChain<SC::ConvertShift<int16, int32, 16>, SC::DecodeInt24<0, littleEndian24> > >(truncated16.data(), 65536, 1, source24.data(), 65536 * 3, 1);

		for(std::size_t i = 0; i < 65536; i++)
		{
			VERIFY_EQUAL_QUIET_NONCONT(sample.sample16()[i], static_cast<int16>(i));
			VERIFY_EQUAL_QUIET_NONCONT(truncated16[i], static_cast<int16>(i));
		}
	}

	// Float 32-Bit
	{
		std::vector<std::byte> source32(65536 * 4);
		for(std::size_t i = 0; i < 65536; i++)
		{
			IEEE754binary32BE floatbits = IEEE754binary32BE((static_cast<float>(i) / 65536.0f) - 0.5f);
			source32[i * 4 + 0] = mpt::byte_cast<std::byte>(floatbits.GetByte(0));
			source32[i * 4 + 1] = mpt::byte_cast<std::byte>(floatbits.GetByte(1));
			source32[i * 4 + 2] = mpt::byte_cast<std::byte>(floatbits.GetByte(2));
			source32[i * 4 + 3] = mpt::byte_cast<std::byte>(floatbits.GetByte(3));
		}

		std::vector<int16> truncated16(65536);
		std::vector<int16> sampleBuf(65536);
		ModSample sample;
		sample.Initialize();
		sample.nLength = 65536;
		sample.uFlags.set(CHN_16BIT);
		sample.pData.pSample = sampleBuf.data();
		DecodeAndNormalizeSample<SC::NormalizationChain<SC::Convert<int16, somefloat32>, SC::DecodeFloat32<bigEndian32> > >(sample, source32.data(), 4*65536);
		DecodeSample<SC::DecodeChain<SC::Convert<int16, somefloat32>, SC::DecodeFloat32<bigEndian32> > >(truncated16.data(), 65536, 1, source32.data(), 65536 * 4, 1);

		for(std::size_t i = 0; i < 65536; i++)
		{
			VERIFY_EQUAL_QUIET_NONCONT(sample.sample16()[i], static_cast<int16>(i - 0x8000u));
			VERIFY_EQUAL_QUIET_NONCONT(std::abs(truncated16[i] - static_cast<int16>((i - 0x8000u) / 2)) <= 1, true);
		}
	}

	// ALaw
	{
		for(unsigned int i = 0; i < 256; ++i)
		{
			std::byte in = mpt::byte_cast<std::byte>(static_cast<uint8>(i));
			std::byte out = mpt::byte_cast<std::byte>(SC::EncodeALaw{}(SC::DecodeInt16ALaw{}(&in)));
			VERIFY_EQUAL_NONCONT(in, out);
		}
		VERIFY_EQUAL_NONCONT(SC::EncodeALaw{}(-32768), SC::EncodeALaw{}(-32256));
		VERIFY_EQUAL_NONCONT(SC::EncodeALaw{}(-32767), SC::EncodeALaw{}(-32256));
		VERIFY_EQUAL_NONCONT(SC::EncodeALaw{}(-1), SC::EncodeALaw{}(-8));
		VERIFY_EQUAL_NONCONT(SC::EncodeALaw{}(0), SC::EncodeALaw{}(8));
		VERIFY_EQUAL_NONCONT(SC::EncodeALaw{}(1), SC::EncodeALaw{}(8));
		VERIFY_EQUAL_NONCONT(SC::EncodeALaw{}(32766), SC::EncodeALaw{}(32256));
		VERIFY_EQUAL_NONCONT(SC::EncodeALaw{}(32767), SC::EncodeALaw{}(32256));
#if 0
		// compare with reference impl
		for (int i = -32768; i <= 32767; ++i)
		{
			VERIFY_EQUAL_NONCONT(SC::EncodeALaw{}(i), mpt::byte_cast<std::byte>(alaw_encode(i)));
		}
#endif
	}

	// uLaw
	{
		for(unsigned int i = 0; i < 256; ++i)
		{
			std::byte in = mpt::byte_cast<std::byte>(static_cast<uint8>(i));
			std::byte out = mpt::byte_cast<std::byte>(SC::EncodeuLaw{}(SC::DecodeInt16uLaw{}(&in)));
			VERIFY_EQUAL_NONCONT(in, out);
		}
#if 0
		// compare with reference impl
		/*
		bool lastMatch = true;
		*/
		for(int i = -32768; i <= 32767; ++i)
		{
			/*
			uint8 mine = mpt::byte_cast<uint8>(SC::EncodeuLaw{}(i));
			uint8 ref = ulaw_encode(i);
			if(lastMatch)
			{
				if(mine == ref)
				{
					VERIFY_EQUAL_NONCONT(mine, ref);
					lastMatch = true;
				} else
				{
					VERIFY_EQUAL_NONCONT(std::abs(static_cast<int>(mine) - static_cast<int>(ref)) <= 1, true);
					lastMatch = false;
				}
			} else
			{
				VERIFY_EQUAL_NONCONT(mine, ref);
				lastMatch = true;
			}
			*/
			//MPT_LOG_GLOBAL(LogNotification, "test", MPT_UFORMAT("{} {} {}")(i, ulaw_encode(i), mpt::byte_cast<uint8>(SC::EncodeuLaw{}(i))));
			VERIFY_EQUAL_NONCONT(SC::EncodeuLaw{}(i), mpt::byte_cast<std::byte>(ulaw_encode(i)));
		}
#endif
	}

	// Range checks
	{
		std::byte oneSample = mpt::byte_cast<std::byte>(static_cast<int8>(1));
		int8 targetBuf4[4];
		int8 *signed8 = targetBuf4;
		std::memset(signed8, 0, 4);
		DecodeSample<SC::DecodeInt8>(targetBuf4, 4, 1, &oneSample, sizeof(oneSample), 1);
		VERIFY_EQUAL_NONCONT(signed8[0], 1);
		VERIFY_EQUAL_NONCONT(signed8[1], 0);
		VERIFY_EQUAL_NONCONT(signed8[2], 0);
		VERIFY_EQUAL_NONCONT(signed8[3], 0);
	}

	// Dither
	{
		std::vector<MixSampleInt> buffer(64);
		DithersOpenMPT dithers(mpt::global_random_device(), 2 /* DitherModPlug */ , 2);
		for(std::size_t i = 0; i < 64; ++i)
		{
			std::visit(
				[&](auto &dither)
				{
					buffer[i] = dither.template process<16>(0, buffer[i]);
				},
				dithers.Variant()
			);
		}
		std::vector<MixSampleInt> expected = {
		    727,
		    -557,
		    -552,
		    -727,
		    439,
		    405,
		    703,
		    -337,
		    235,
		    -776,
		    -458,
		    905,
		    -110,
		    158,
		    374,
		    -362,
		    283,
		    306,
		    710,
		    304,
		    -608,
		    536,
		    -501,
		    -593,
		    -349,
		    812,
		    916,
		    53,
		    -953,
		    881,
		    -236,
		    -20,
		    -623,
		    -895,
		    -302,
		    -415,
		    899,
		    -948,
		    -766,
		    -186,
		    -390,
		    -169,
		    253,
		    -622,
		    -769,
		    -1001,
		    1019,
		    787,
		    -239,
		    718,
		    -423,
		    988,
		    -91,
		    763,
		    -933,
		    -510,
		    484,
		    794,
		    -340,
		    552,
		    866,
		    -608,
		    35,
		    395};
		for(std::size_t i = 0; i < 64; ++i)
		{
			VERIFY_EQUAL_QUIET_NONCONT(buffer[i], expected[i]);
		}
	}
}


static void TestMIDIMacroParser()
{
	uint8 rawData[] = {0x90, 0x40, 0x70, 0x50, 0x70, 0xF5, 0xF6, 0x60, 0x70, 0xF0};
	MIDIMacroParser rawParser{mpt::as_span(rawData)};
	mpt::span<uint8> midiMsg;
	rawParser.NextMessage(midiMsg);
	VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array<uint8, 3>{0x90, 0x40, 0x70}), true);
	rawParser.NextMessage(midiMsg);
	VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array<uint8, 3>{0x90, 0x50, 0x70}), true);
	rawParser.NextMessage(midiMsg);
	VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array<uint8, 1>{0xF5}), true);
	rawParser.NextMessage(midiMsg);
	VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array<uint8, 1>{0xF6}), true);
	rawParser.NextMessage(midiMsg);
	VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array<uint8, 3>{0x90, 0x60, 0x70}), true);
	rawParser.NextMessage(midiMsg);
	VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array<uint8, 1>{0xF0}), true);
	VERIFY_EQUAL_NONCONT(rawParser.NextMessage(midiMsg), false);

	mpt::heap_value<CSoundFile> sndFile;
	mpt::heap_value<PlayState> playState;
	mpt::heap_value<ModInstrument> instr;
	sndFile->Create(MOD_TYPE_MPT, 2);
	playState->m_nGlobalVolume = MAX_GLOBAL_VOLUME / 2;
	playState->Chn[3].nMasterChn = 2;
	playState->Chn[3].nLastNote = NOTE_MIN + 0x60;
	playState->Chn[3].nVolume = 193;
	playState->Chn[3].nVolSwing = 32;
	playState->Chn[3].nCalcVolume = 8192;
	playState->Chn[3].nGlobalVol = 32;
	playState->Chn[3].nInsVol = 32;
	playState->Chn[3].nPan = 32;
	playState->Chn[3].nRealPan = 192;
	playState->Chn[3].oldOffset = 0x123456;
	playState->Chn[3].pModInstrument = instr.get();
	instr->nMidiChannel = MidiLastChannel;
	instr->nMidiProgram = 1 + 9;
	instr->wMidiBank = 1 + 130;
	const char macro[] = "r42 9c z v 50 h F5 F6 n u Bc a b xyop F0 41 10 00 10 12 10 00 04 00 2 s";
	std::vector<uint8> out(std::size(macro) + 1);
	MIDIMacroParser macroParser{*sndFile, playState.get(), 3, false, mpt::as_span(macro), mpt::as_span(out), 64, 0};
	macroParser.NextMessage(midiMsg);
	VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array<uint8, 3>{0x9F, 0x40, 0x0E}), true);
	macroParser.NextMessage(midiMsg);
	VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array<uint8, 3>{0x9F, 0x50, 0x01}), true);
	macroParser.NextMessage(midiMsg);
	VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array<uint8, 1>{0xF5}), true);
	macroParser.NextMessage(midiMsg);
	VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array<uint8, 1>{0xF6}), true);
	macroParser.NextMessage(midiMsg);
	VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array<uint8, 3>{0x9F, 0x60, 0x08}), true);
	macroParser.NextMessage(midiMsg);
	VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array<uint8, 3>{0xBF, 0x01, 0x02}), true);
	macroParser.NextMessage(midiMsg);
	VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array<uint8, 3>{0xBF, 0x10, 0x60}), true);
	macroParser.NextMessage(midiMsg);
	VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array<uint8, 3>{0xBF, 0x34, 0x09}), true);
	macroParser.NextMessage(midiMsg);
	VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array<uint8, 13>{0xF0, 0x41, 0x10, 0x00, 0x10, 0x12, 0x10, 0x00, 0x04, 0x00, 0x02, 0x6A, 0xF7}), true);
	VERIFY_EQUAL_NONCONT(macroParser.NextMessage(midiMsg), false);
}


} // namespace Test

OPENMPT_NAMESPACE_END

#else //Case: ENABLE_TESTS is not defined.

OPENMPT_NAMESPACE_BEGIN

namespace Test {

void DoTests()
{
	return;
}

} // namespace Test

OPENMPT_NAMESPACE_END

#endif
