| /* |
| * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. |
| * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. |
| */ |
| |
| package test.text |
| |
| import kotlin.test.Test |
| import kotlin.test.assertEquals |
| import kotlin.test.assertFailsWith |
| |
| class NumberHexFormatTest { |
| |
| companion object { |
| private const val longHex3a: String = "000000000000003a" // 16 hex digits |
| private const val longHex3A: String = "000000000000003A" // 16 hex digits |
| } |
| |
| private fun testFormatAndParse(number: Long, digits: String, format: HexFormat) { |
| testFormat(number, digits, format) |
| testParse(digits, number, format) |
| } |
| |
| private fun testFormat(number: Long, digits: String, format: HexFormat) { |
| fun String.withPrefixAndSuffix(): String = format.number.prefix + this + format.number.suffix |
| |
| assertEquals(digits.takeLast(2).withPrefixAndSuffix(), number.toByte().toHexString(format)) |
| assertEquals(digits.takeLast(2).withPrefixAndSuffix(), number.toUByte().toHexString(format)) |
| assertEquals(digits.takeLast(4).withPrefixAndSuffix(), number.toShort().toHexString(format)) |
| assertEquals(digits.takeLast(4).withPrefixAndSuffix(), number.toUShort().toHexString(format)) |
| assertEquals(digits.takeLast(8).withPrefixAndSuffix(), number.toInt().toHexString(format)) |
| assertEquals(digits.takeLast(8).withPrefixAndSuffix(), number.toUInt().toHexString(format)) |
| assertEquals(digits.withPrefixAndSuffix(), number.toHexString(format)) |
| assertEquals(digits.withPrefixAndSuffix(), number.toULong().toHexString(format)) |
| } |
| |
| private fun testParse(digits: String, number: Long, format: HexFormat) { |
| fun String.withPrefixAndSuffix(): String = format.number.prefix + this + format.number.suffix |
| |
| assertEquals(number.toByte(), digits.takeLast(2).withPrefixAndSuffix().hexToByte(format)) |
| assertEquals(number.toUByte(), digits.takeLast(2).withPrefixAndSuffix().hexToUByte(format)) |
| assertEquals(number.toShort(), digits.takeLast(4).withPrefixAndSuffix().hexToShort(format)) |
| assertEquals(number.toUShort(), digits.takeLast(4).withPrefixAndSuffix().hexToUShort(format)) |
| assertEquals(number.toInt(), digits.takeLast(8).withPrefixAndSuffix().hexToInt(format)) |
| assertEquals(number.toUInt(), digits.takeLast(8).withPrefixAndSuffix().hexToUInt(format)) |
| assertEquals(number, digits.withPrefixAndSuffix().hexToLong(format)) |
| assertEquals(number.toULong(), digits.withPrefixAndSuffix().hexToULong(format)) |
| } |
| |
| @Test |
| fun ignoreBytesFormat() { |
| val format = HexFormat { |
| bytes { |
| bytesPerLine = 10 |
| bytesPerGroup = 4 |
| groupSeparator = "---" |
| byteSeparator = " " |
| bytePrefix = "#" |
| byteSuffix = ";" |
| } |
| } |
| |
| testFormatAndParse(58, longHex3a, format) |
| } |
| |
| @Test |
| fun upperCase() { |
| testFormatAndParse(58, longHex3A, HexFormat { upperCase = true }) |
| testFormatAndParse(58, longHex3A, HexFormat.UpperCase) |
| } |
| |
| @Test |
| fun lowerCase() { |
| testFormatAndParse(58, longHex3a, HexFormat { upperCase = false }) |
| } |
| |
| @Test |
| fun defaultCase() { |
| testFormatAndParse(58, longHex3a, HexFormat.Default) |
| } |
| |
| @Test |
| fun formatAndParseZero() { |
| testFormatAndParse(0, "0".repeat(16), HexFormat.Default) |
| testParse("0", 0, HexFormat.Default) |
| } |
| |
| @Test |
| fun formatAndParseMax() { |
| testFormatAndParse(Long.MAX_VALUE, "7" + "f".repeat(15), HexFormat.Default) |
| testFormatAndParse(-1, "f".repeat(16), HexFormat.Default) |
| } |
| |
| @Test |
| fun formatAndParsePrefixSuffix() { |
| testFormatAndParse(58, "000000000000003a", HexFormat { number.prefix = "0x" }) |
| testFormatAndParse(58, "000000000000003a", HexFormat { number.suffix = "h" }) |
| testFormatAndParse(58, "000000000000003a", HexFormat { number.prefix = "0x"; number.suffix = "h" }) |
| } |
| |
| @Test |
| fun removeLeadingZeros() { |
| val format = HexFormat { number.removeLeadingZeros = true } |
| testFormatAndParse(58, "3a", format) |
| testFormatAndParse(0, "0", format) |
| } |
| |
| @Test |
| fun formatMinLength() { |
| // minLength < typeHexLength |
| assertEquals(longHex3a, 58L.toHexString(HexFormat { number.minLength = 15 })) |
| assertEquals(longHex3a.takeLast(15), 58L.toHexString(HexFormat { number { minLength = 15; removeLeadingZeros = true } })) |
| assertEquals("3a", 58L.toHexString(HexFormat { number { minLength = 1; removeLeadingZeros = true } })) |
| |
| // minLength == typeHexLength |
| assertEquals("003a", 58.toShort().toHexString(HexFormat { number.minLength = 4 })) |
| assertEquals("003a", 58.toShort().toHexString(HexFormat { number { minLength = 4; removeLeadingZeros = true } })) |
| |
| // minLength > typeHexLength |
| assertEquals(longHex3a, 58.toHexString(HexFormat { number.minLength = 16 })) |
| assertEquals(longHex3a, 58.toHexString(HexFormat { number { minLength = 16; removeLeadingZeros = true } })) |
| } |
| |
| @Test |
| fun parseLongFromSubstring() { |
| val url = "https://magnuschatt.medium.com/why-you-should-totally-switch-to-kotlin-c7bbde9e10d5" |
| val articleId = url.substringAfterLast('-').hexToLong() |
| assertEquals(0xc7bbde9e10d5, articleId) |
| } |
| |
| // Number parsing strictness |
| |
| @Test |
| fun parseRequiresPrefixSuffix() { |
| assertEquals(58, "0x0000003a".hexToInt(HexFormat { number.prefix = "0x" })) |
| assertEquals(58, "0x3a".hexToInt(HexFormat { number.prefix = "0x" })) |
| assertEquals(10, "3a".hexToInt(HexFormat { number.prefix = "3" })) |
| assertFailsWith<NumberFormatException> { |
| "0000003a".hexToInt(HexFormat { number.prefix = "0x" }) |
| } |
| assertEquals(58, "0000003ah".hexToInt(HexFormat { number.suffix = "h" })) |
| assertEquals(58, "3ah".hexToInt(HexFormat { number.suffix = "h" })) |
| assertEquals(3, "3a".hexToInt(HexFormat { number.suffix = "a" })) |
| assertFailsWith<NumberFormatException> { |
| "0000003a".hexToInt(HexFormat { number.suffix = "h" }) |
| } |
| } |
| |
| @Test |
| fun parseInvalidDigit() { |
| assertFailsWith<NumberFormatException> { "3g".hexToByte() } |
| assertFailsWith<NumberFormatException> { "3g".hexToShort() } |
| assertFailsWith<NumberFormatException> { "3g".hexToInt() } |
| assertFailsWith<NumberFormatException> { "3g".hexToLong() } |
| } |
| |
| @Test |
| fun parseRequiresAtLeastOneHexDigit() { |
| assertFailsWith<NumberFormatException> { |
| "".hexToInt() |
| } |
| assertFailsWith<NumberFormatException> { |
| "3a".hexToInt(HexFormat { number { prefix = "3"; suffix = "a" } }) |
| } |
| assertFailsWith<NumberFormatException> { |
| "3a".hexToInt(HexFormat { number.prefix = "3a" }) |
| } |
| assertFailsWith<NumberFormatException> { |
| "3a".hexToInt(HexFormat { number.suffix = "3a" }) |
| } |
| assertEquals(15, "0xfh".hexToInt(HexFormat { number { prefix = "0x"; suffix = "h" } })) |
| } |
| |
| @Test |
| fun parseIgnoresCase() { |
| assertEquals(58, "0000003a".hexToInt()) |
| assertEquals(58, "3a".hexToInt()) |
| assertEquals(58, "0000003A".hexToInt()) |
| assertEquals(58, "3A".hexToInt()) |
| |
| val format = HexFormat { |
| upperCase = true |
| number { |
| prefix = "0X" |
| suffix = "h" |
| } |
| } |
| assertEquals(58, "0X0000003AH".hexToInt(format)) |
| assertEquals(58, "0x3Ah".hexToInt(format)) |
| assertEquals(58, "0X0000003aH".hexToInt(format)) |
| assertEquals(58, "0x3ah".hexToInt(format)) |
| } |
| |
| @Test |
| fun parseIgnoresRemoveLeadingZeros() { |
| assertEquals(58, "3a".hexToInt()) |
| assertEquals(58, "3a".hexToInt(HexFormat { number.removeLeadingZeros = false })) |
| assertEquals(58, "0000003a".hexToInt(HexFormat { number.removeLeadingZeros = true })) |
| } |
| |
| @Test |
| fun parseIgnoresMinLength() { |
| assertEquals(58, "3a".hexToInt(HexFormat { number.minLength = 4 })) |
| assertEquals(58, "3a".hexToInt(HexFormat { number.minLength = 8 })) |
| assertEquals(58L, "3a".hexToLong(HexFormat { number.minLength = 20 })) |
| } |
| |
| @Test |
| fun parseRequiresValueFitTheType() { |
| val zeros = "0".repeat(20) |
| val fs = "f".repeat(20) |
| |
| fun <T> test(typeHexLength: Int, toType: Long.() -> T, hexToType: String.() -> T) { |
| assertEquals(0L.toType(), zeros.hexToType()) |
| |
| val maxHex = fs.take(typeHexLength) |
| assertEquals((-1L).toType(), maxHex.hexToType()) |
| |
| val paddedMaxHex = "${zeros}${maxHex}" |
| assertEquals((-1L).toType(), paddedMaxHex.hexToType()) |
| |
| val hexLengthZeros = fs.take(typeHexLength) |
| assertFailsWith<NumberFormatException> { |
| "1$hexLengthZeros".hexToType() |
| } |
| assertFailsWith<NumberFormatException> { |
| "${zeros}1$hexLengthZeros".hexToType() |
| } |
| } |
| |
| test(2, Long::toByte, String::hexToByte) |
| test(2, Long::toUByte, String::hexToUByte) |
| test(4, Long::toShort, String::hexToShort) |
| test(4, Long::toUShort, String::hexToUShort) |
| test(8, Long::toInt, String::hexToInt) |
| test(8, Long::toUInt, String::hexToUInt) |
| test(16, Long::toLong, String::hexToLong) |
| test(16, Long::toULong, String::hexToULong) |
| } |
| |
| // Invalid HexFormat configuration |
| |
| @Test |
| fun prefixWithNewLine() { |
| assertFailsWith<IllegalArgumentException> { HexFormat { number.prefix = "\n" } } |
| assertFailsWith<IllegalArgumentException> { HexFormat { number.prefix = "\r" } } |
| } |
| |
| @Test |
| fun suffixWithNewLine() { |
| assertFailsWith<IllegalArgumentException> { HexFormat { number.suffix = "ab\ncd" } } |
| assertFailsWith<IllegalArgumentException> { HexFormat { number.suffix = "ab\rcd" } } |
| } |
| |
| @Test |
| fun minLengthNonPositive() { |
| assertFailsWith<IllegalArgumentException> { HexFormat { number.minLength = 0 } } |
| assertFailsWith<IllegalArgumentException> { HexFormat { number.minLength = -1 } } |
| assertFailsWith<IllegalArgumentException> { HexFormat { number.minLength = Int.MIN_VALUE } } |
| } |
| } |