blob: b79551e83416ec7377d14c82ffc23306df1fab96 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/formats/hls/types.h"
#include "base/location.h"
#include "media/formats/hls/source_string.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media::hls {
namespace {
types::VariableName CreateVarName(base::StringPiece name) {
return types::VariableName::Parse(SourceString::CreateForTesting(name))
.value();
}
} // namespace
TEST(HlsFormatParserTest, ParseDecimalIntegerTest) {
auto const error_test = [](base::StringPiece input,
const base::Location& from =
base::Location::Current()) {
auto result =
types::ParseDecimalInteger(SourceString::CreateForTesting(1, 1, input));
ASSERT_TRUE(result.has_error()) << from.ToString();
auto error = std::move(result).error();
EXPECT_EQ(error.code(), ParseStatusCode::kFailedToParseDecimalInteger);
};
auto const ok_test = [](base::StringPiece input,
types::DecimalInteger expected,
const base::Location& from =
base::Location::Current()) {
auto result =
types::ParseDecimalInteger(SourceString::CreateForTesting(1, 1, input));
ASSERT_TRUE(result.has_value()) << from.ToString();
auto value = std::move(result).value();
EXPECT_EQ(value, expected);
};
// Empty string is not allowed
error_test("");
// Decimal-integers may not be quoted
error_test("'90132409'");
error_test("\"12309234\"");
// Decimal-integers may not be negative
error_test("-81234");
// Decimal-integers may not contain junk or leading/trailing spaces
error_test("12.352334");
error_test(" 12352334");
error_test("2352334 ");
error_test("235.2334");
error_test("+2352334");
error_test("235x2334");
// Decimal-integers may not exceed 20 characters
error_test("000000000000000000001");
// Test some valid inputs
ok_test("00000000000000000001", 1);
ok_test("0", 0);
ok_test("1", 1);
ok_test("2334509345", 2334509345);
// Test max supported value
ok_test("18446744073709551615", 18446744073709551615u);
error_test("18446744073709551616");
}
TEST(HlsFormatParserTest, ParseDecimalFloatingPointTest) {
auto const error_test = [](base::StringPiece input,
const base::Location& from =
base::Location::Current()) {
auto result = types::ParseDecimalFloatingPoint(
SourceString::CreateForTesting(1, 1, input));
ASSERT_TRUE(result.has_error()) << from.ToString();
auto error = std::move(result).error();
EXPECT_EQ(error.code(),
ParseStatusCode::kFailedToParseDecimalFloatingPoint);
};
auto const ok_test = [](base::StringPiece input,
types::DecimalFloatingPoint expected,
const base::Location& from =
base::Location::Current()) {
auto result = types::ParseDecimalFloatingPoint(
SourceString::CreateForTesting(1, 1, input));
ASSERT_TRUE(result.has_value()) << from.ToString();
auto value = std::move(result).value();
EXPECT_DOUBLE_EQ(value, expected);
};
// Empty string is not allowed
error_test("");
// Decimal-floating-point may not be quoted
error_test("'901.32409'");
error_test("\"123092.34\"");
// Decimal-floating-point may not be negative */
error_test("-812.34");
// Decimal-floating-point may not contain junk or leading/trailing spaces
error_test("+12352334");
error_test(" 123.45");
error_test("123.45 ");
error_test("235x2334");
error_test("+2352334");
// Test some valid inputs
ok_test("0", 0.0);
ok_test("00.00", 0.0);
ok_test("42", 42.0);
ok_test("42.0", 42.0);
ok_test("42.", 42.0);
ok_test("0.75", 0.75);
ok_test(".75", 0.75);
ok_test("12312309123.908908234", 12312309123.908908234);
ok_test("0000000.000001", 0.000001);
}
TEST(HlsFormatParserTest, ParseSignedDecimalFloatingPointTest) {
auto const error_test = [](base::StringPiece input,
const base::Location& from =
base::Location::Current()) {
auto result = types::ParseSignedDecimalFloatingPoint(
SourceString::CreateForTesting(1, 1, input));
ASSERT_TRUE(result.has_error()) << from.ToString();
auto error = std::move(result).error();
EXPECT_EQ(error.code(),
ParseStatusCode::kFailedToParseSignedDecimalFloatingPoint);
};
auto const ok_test = [](base::StringPiece input,
types::SignedDecimalFloatingPoint expected,
const base::Location& from =
base::Location::Current()) {
auto result = types::ParseSignedDecimalFloatingPoint(
SourceString::CreateForTesting(1, 1, input));
ASSERT_TRUE(result.has_value()) << from.ToString();
auto value = std::move(result).value();
EXPECT_DOUBLE_EQ(value, expected);
};
// Empty string is not allowed
error_test("");
// Signed-decimal-floating-point may not be quoted
error_test("'901.32409'");
error_test("\"123092.34\"");
// Signed-decimal-floating-point may not contain junk or leading/trailing
// spaces
error_test("+12352334");
error_test(" 123.45");
error_test("123.45 ");
error_test("235x2334");
error_test("+2352334");
// Test some valid inputs
ok_test("0", 0.0);
ok_test("00.00", 0.0);
ok_test("42", 42.0);
ok_test("-42", -42.0);
ok_test("42.0", 42.0);
ok_test("75.", 75.0);
ok_test("0.75", 0.75);
ok_test("-0.75", -0.75);
ok_test("-.75", -0.75);
ok_test(".75", 0.75);
ok_test("-75.", -75.0);
ok_test("12312309123.908908234", 12312309123.908908234);
ok_test("0000000.000001", 0.000001);
}
TEST(HlsFormatParserTest, AttributeListIteratorTest) {
using Items =
std::initializer_list<std::pair<base::StringPiece, base::StringPiece>>;
auto run_test = [](auto str, Items items, ParseStatusCode error,
const auto& from) {
types::AttributeListIterator iter(SourceString::CreateForTesting(str));
for (auto item : items) {
auto result = iter.Next();
ASSERT_TRUE(result.has_value()) << from.ToString();
auto value = std::move(result).value();
EXPECT_EQ(value.name.Str(), item.first);
EXPECT_EQ(value.value.Str(), item.second);
}
// Afterwards, iterator should fail
auto result = iter.Next();
ASSERT_TRUE(result.has_error());
EXPECT_EQ(std::move(result).error().code(), error);
result = iter.Next();
ASSERT_TRUE(result.has_error());
EXPECT_EQ(std::move(result).error().code(), error);
};
// Checks for valid items, followed by an error
auto error_test = [=](auto str, Items items,
const base::Location& from =
base::Location::Current()) {
run_test(str, items, ParseStatusCode::kMalformedAttributeList, from);
};
// Checks for valid items, followed by EOF
auto ok_test = [=](auto str, Items items,
const base::Location& from = base::Location::Current()) {
run_test(str, items, ParseStatusCode::kReachedEOF, from);
};
ok_test("", {});
ok_test(R"(HELLO=WORLD)", {{"HELLO", "WORLD"}});
ok_test(R"(HELLO=WORLD,)", {{"HELLO", "WORLD"}});
ok_test(R"(HELLO="WORLD")", {{"HELLO", "\"WORLD\""}});
ok_test(R"(HE-LLO=foo,WORLD=2134)", {{"HE-LLO", "foo"}, {"WORLD", "2134"}});
ok_test(R"(-HELLO-="",WORLD=.21-34,)",
{{"-HELLO-", "\"\""}, {"WORLD", ".21-34"}});
ok_test(R"(1HELLO=".zxc09,1,23%${}",2WORLD=3)",
{{"1HELLO", "\".zxc09,1,23%${}\""}, {"2WORLD", "3"}});
ok_test(R"(HELLO=" W O R L D ")", {{"HELLO", "\" W O R L D \""}});
ok_test(R"(HELLO="x",WORLD="y")", {{"HELLO", "\"x\""}, {"WORLD", "\"y\""}});
// Attribute names may not be empty
error_test(R"(=BAR,HELLO=WORLD)", {});
// Attribute values may not be empty
error_test(R"(FOO=,HELLO=WORLD)", {});
error_test(R"(FOO=BAR,HELLO=)", {{"FOO", "BAR"}});
// Attribute names may not have lowercase letters
error_test(R"(FOO=BAR,HeLLO=WORLD)", {{"FOO", "BAR"}});
error_test(R"(FOO=BAR,hello=WORLD)", {{"FOO", "BAR"}});
// Attribute names may not have other characters
error_test(R"(FOO=BAR,HEL.LO=WORLD)", {{"FOO", "BAR"}});
error_test(R"(FOO=BAR,HEL$LO=WORLD)", {{"FOO", "BAR"}});
error_test(R"(FOO=BAR,HEL(LO=WORLD)", {{"FOO", "BAR"}});
// Attribute names may not have leading, trailing, or interior whitespace
error_test(R"(FOO=BAR, HELLO=WORLD)", {{"FOO", "BAR"}});
error_test(R"(FOO=BAR,HELLO =WORLD)", {{"FOO", "BAR"}});
error_test(R"(FOO=BAR,HE LLO=WORLD)", {{"FOO", "BAR"}});
// Attribute names must be followed by an equals sign
error_test(R"(FOO=BAR,HELLOWORLD,)", {{"FOO", "BAR"}});
// Attribute values may not contain leading, interior, or trailing whitespace
error_test(R"(FOO=BAR,HELLO= WORLD,)", {{"FOO", "BAR"}});
error_test(R"(FOO=BAR,HELLO=WO RLD,)", {{"FOO", "BAR"}});
error_test(R"(FOO=BAR,HELLO=WORLD ,)", {{"FOO", "BAR"}});
// Leading commas are not allowed
error_test(R"(,FOO=BAR,HELLO=WORLD,)", {});
// A single trailing comma is allowed, multiple are not
error_test(R"(FOO=BAR,HELLO=WORLD,,)", {{"FOO", "BAR"}, {"HELLO", "WORLD"}});
// Single-quotes are not allowed unquoted
error_test(R"(FOO='hahaha')", {});
ok_test(R"(FOO="'hahaha'")", {{"FOO", "\"'hahaha'\""}});
// Unmatched double-quote is not allowed
error_test(R"(FOO=")", {});
error_test(R"(FOO=BAR"BAZ)", {});
error_test(R"(FOO=BAR")", {});
// Double-quote (even escaped) inside double-quotes is not allowed
error_test(R"(FOO=""")", {});
error_test(R"(FOO="\"")", {});
// Empty quoted-string is allowed
ok_test(R"(FOO="")", {{"FOO", "\"\""}});
// Tabs inside quotes are allowed
ok_test("FOO=\"\t\"", {{"FOO", "\"\t\""}});
// Linefeed or carriage return inside quotes are not allowed
error_test("FOO=\"as\rdf\"", {});
error_test("FOO=\"as\ndf\"", {});
}
TEST(HlsFormatParserTest, AttributeMapTest) {
auto make_iter = [](auto str) {
return types::AttributeListIterator(SourceString::CreateForTesting(str));
};
auto run_fill = [](auto& storage, auto* iter) {
types::AttributeMap map(storage);
return map.Fill(iter);
};
// Test that AttributeMap fills all entries
{
auto storage = types::AttributeMap::MakeStorage("BAR", "BAZ", "FOO");
auto iter = make_iter("FOO=foo,BAR=bar,BAZ=baz");
auto result = run_fill(storage, &iter);
EXPECT_TRUE(result.has_error());
EXPECT_EQ(std::move(result).error().code(), ParseStatusCode::kReachedEOF);
EXPECT_TRUE(storage[0].second.has_value());
EXPECT_EQ(storage[0].second.value().Str(), "bar");
EXPECT_TRUE(storage[1].second.has_value());
EXPECT_EQ(storage[1].second.value().Str(), "baz");
EXPECT_TRUE(storage[2].second.has_value());
EXPECT_EQ(storage[2].second.value().Str(), "foo");
}
// Test that AttributeMap doesn't touch missing entries
{
auto storage = types::AttributeMap::MakeStorage("CAR", "CAZ", "COO", "GOO");
auto iter = make_iter("COO=coo,CAR=car,CAZ=caz");
auto result = run_fill(storage, &iter);
EXPECT_TRUE(result.has_error());
EXPECT_EQ(std::move(result).error().code(), ParseStatusCode::kReachedEOF);
EXPECT_TRUE(storage[0].second.has_value());
EXPECT_EQ(storage[0].second.value().Str(), "car");
EXPECT_TRUE(storage[1].second.has_value());
EXPECT_EQ(storage[1].second.value().Str(), "caz");
EXPECT_TRUE(storage[2].second.has_value());
EXPECT_EQ(storage[2].second.value().Str(), "coo");
EXPECT_FALSE(storage[3].second.has_value());
}
// Test that attribute map returns unexpected entries
{
auto storage = types::AttributeMap::MakeStorage("DAR", "DAZ", "DOO");
auto iter = make_iter("DOO=doo,GOO=goo,DAR=dar,DAZ=daz,");
auto result = run_fill(storage, &iter);
EXPECT_TRUE(result.has_value());
auto item = std::move(result).value();
EXPECT_EQ(item.name.Str(), "GOO");
EXPECT_EQ(item.value.Str(), "goo");
EXPECT_FALSE(storage[0].second.has_value());
EXPECT_FALSE(storage[1].second.has_value());
EXPECT_TRUE(storage[2].second.has_value());
EXPECT_EQ(storage[2].second.value().Str(), "doo");
result = run_fill(storage, &iter);
EXPECT_TRUE(result.has_error());
EXPECT_EQ(std::move(result).error().code(), ParseStatusCode::kReachedEOF);
EXPECT_TRUE(storage[0].second.has_value());
EXPECT_EQ(storage[0].second.value().Str(), "dar");
EXPECT_TRUE(storage[1].second.has_value());
EXPECT_EQ(storage[1].second.value().Str(), "daz");
EXPECT_TRUE(storage[2].second.has_value());
EXPECT_EQ(storage[2].second.value().Str(), "doo");
}
// Test that the attribute map handles duplicate entries
{
auto storage = types::AttributeMap::MakeStorage("EAR", "EAZ", "EOO");
auto iter = make_iter("EOO=eoo,EAR=ear,EOO=eoo2,EAZ=eaz,");
auto result = run_fill(storage, &iter);
EXPECT_TRUE(result.has_error());
EXPECT_EQ(std::move(result).error().code(),
ParseStatusCode::kAttributeListHasDuplicateNames);
EXPECT_TRUE(storage[0].second.has_value());
EXPECT_EQ(storage[0].second.value().Str(), "ear");
EXPECT_FALSE(storage[1].second.has_value());
EXPECT_TRUE(storage[2].second.has_value());
EXPECT_EQ(storage[2].second.value().Str(), "eoo");
// Calling again should result in the same error
result = run_fill(storage, &iter);
EXPECT_TRUE(result.has_error());
EXPECT_EQ(std::move(result).error().code(),
ParseStatusCode::kAttributeListHasDuplicateNames);
EXPECT_TRUE(storage[0].second.has_value());
EXPECT_EQ(storage[0].second.value().Str(), "ear");
EXPECT_FALSE(storage[1].second.has_value());
EXPECT_TRUE(storage[2].second.has_value());
EXPECT_EQ(storage[2].second.value().Str(), "eoo");
}
// Test that the attribute map forwards errors to the caller
{
auto storage = types::AttributeMap::MakeStorage("FAR", "FAZ", "FOO");
auto iter = make_iter("FOO=foo,FAR=far ,FAZ=faz,");
auto result = run_fill(storage, &iter);
EXPECT_TRUE(result.has_error());
EXPECT_EQ(std::move(result).error().code(),
ParseStatusCode::kMalformedAttributeList);
EXPECT_FALSE(storage[0].second.has_value());
EXPECT_FALSE(storage[1].second.has_value());
EXPECT_TRUE(storage[2].second.has_value());
EXPECT_EQ(storage[2].second.value().Str(), "foo");
// Calling again should return same error
result = run_fill(storage, &iter);
EXPECT_TRUE(result.has_error());
EXPECT_EQ(std::move(result).error().code(),
ParseStatusCode::kMalformedAttributeList);
EXPECT_FALSE(storage[0].second.has_value());
EXPECT_FALSE(storage[1].second.has_value());
EXPECT_TRUE(storage[2].second.has_value());
EXPECT_EQ(storage[2].second.value().Str(), "foo");
}
}
TEST(HlsFormatParserTest, ParseVariableNameTest) {
auto const ok_test = [](base::StringPiece input,
const base::Location& from =
base::Location::Current()) {
auto result =
types::VariableName::Parse(SourceString::CreateForTesting(input));
ASSERT_TRUE(result.has_value()) << from.ToString();
EXPECT_EQ(std::move(result).value().GetName(), input);
};
auto const error_test = [](base::StringPiece input,
const base::Location& from =
base::Location::Current()) {
auto result =
types::VariableName::Parse(SourceString::CreateForTesting(input));
ASSERT_TRUE(result.has_error()) << from.ToString();
EXPECT_EQ(std::move(result).error().code(),
ParseStatusCode::kMalformedVariableName);
};
// Variable names may not be empty
error_test("");
// Variable names may not have whitespace
error_test(" Hello");
error_test("He llo");
error_test("Hello ");
// Variable names may not have characters outside the allowed set
error_test("He*llo");
error_test("!Hello");
error_test("He$o");
error_test("He=o");
// Test some valid variable names
ok_test("Hello");
ok_test("HE-LLO");
ok_test("__H3LL0__");
ok_test("12345");
ok_test("-1_2-3_4-5_");
ok_test("______-___-__---");
}
TEST(HlsFormatParserTest, ParseQuotedStringWithoutSubstitutionTest) {
const auto ok_test = [](base::StringPiece in,
base::StringPiece expected_out) {
auto in_str = SourceString::CreateForTesting(in);
auto out = types::ParseQuotedStringWithoutSubstitution(in_str);
ASSERT_TRUE(out.has_value());
EXPECT_EQ(std::move(out).value().Str(), expected_out);
};
const auto error_test = [](base::StringPiece in) {
auto in_str = SourceString::CreateForTesting(in);
auto out = types::ParseQuotedStringWithoutSubstitution(in_str);
ASSERT_TRUE(out.has_error());
EXPECT_EQ(std::move(out).error().code(),
ParseStatusCode::kFailedToParseQuotedString);
};
// Test some basic examples
ok_test("\"\"", "");
ok_test("\" \"", " ");
ok_test("\"Hello, world!\"", "Hello, world!");
// Interior quotes are not checked by this function
ok_test("\"Hello, \"World!\"\"", "Hello, \"World!\"");
// Variables are not substituted by this function, and do not trigger an error
ok_test("\"Hello, {$WORLD}\"", "Hello, {$WORLD}");
// Single-quoted string is not allowed
error_test("''");
error_test("' '");
error_test("'Hello, world!'");
// Missing leading/trailing quote is not allowed
error_test("\"");
error_test("\" ");
error_test(" \"");
error_test("\"Hello, world!");
error_test("Hello, world!\"");
// Empty string is not allowed
error_test("");
}
TEST(HlsFormatParserTest, ParseQuotedStringTest) {
VariableDictionary dict;
EXPECT_TRUE(dict.Insert(CreateVarName("FOO"), "bar"));
EXPECT_TRUE(dict.Insert(CreateVarName("BAZ"), "\"foo\""));
const auto ok_test = [&dict](base::StringPiece in,
base::StringPiece expected_out) {
auto in_str = SourceString::CreateForTesting(in);
VariableDictionary::SubstitutionBuffer sub_buffer;
auto out = types::ParseQuotedString(in_str, dict, sub_buffer);
ASSERT_TRUE(out.has_value());
EXPECT_EQ(std::move(out).value(), expected_out);
};
const auto error_test = [&dict](base::StringPiece in,
ParseStatusCode expected_error) {
auto in_str = SourceString::CreateForTesting(in);
VariableDictionary::SubstitutionBuffer sub_buffer;
auto out = types::ParseQuotedString(in_str, dict, sub_buffer);
ASSERT_TRUE(out.has_error());
EXPECT_EQ(std::move(out).error().code(), expected_error);
};
// Test some basic examples
ok_test("\"\"", "");
ok_test("\" \"", " ");
ok_test("\"Hello, world!\"", "Hello, world!");
// Interior quotes are not checked by this function
ok_test("\"Hello, \"World!\"\"", "Hello, \"World!\"");
// Variables ARE substituted by this function
ok_test("\"Hello, {$FOO}\"", "Hello, bar");
ok_test("\"Hello, {$BAZ}\"", "Hello, \"foo\"");
error_test("\"Hello, {$foo}\"", ParseStatusCode::kVariableUndefined);
// Single-quoted string is not allowed
error_test("''", ParseStatusCode::kFailedToParseQuotedString);
error_test("' '", ParseStatusCode::kFailedToParseQuotedString);
error_test("'Hello, world!'", ParseStatusCode::kFailedToParseQuotedString);
// Missing leading/trailing quote is not allowed
error_test("\"", ParseStatusCode::kFailedToParseQuotedString);
error_test("\" ", ParseStatusCode::kFailedToParseQuotedString);
error_test(" \"", ParseStatusCode::kFailedToParseQuotedString);
error_test("\"Hello, world!", ParseStatusCode::kFailedToParseQuotedString);
error_test("Hello, world!\"", ParseStatusCode::kFailedToParseQuotedString);
// Empty string is not allowed
error_test("", ParseStatusCode::kFailedToParseQuotedString);
}
} // namespace media::hls