GH-1047 parse world files and integrate MCEdit with world page

This commit is contained in:
Petr Mrázek
2015-09-06 23:35:58 +02:00
parent 40b233448c
commit 38693e1d6c
58 changed files with 6079 additions and 161 deletions

View File

@ -0,0 +1,22 @@
enable_testing()
include_directories(${libnbt++_SOURCE_DIR}/include)
include_directories(${CXXTEST_INCLUDE_DIR})
CXXTEST_ADD_TEST(nbttest nbttest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/nbttest.h)
target_link_libraries(nbttest nbt++)
CXXTEST_ADD_TEST(endian_str_test endian_str_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/endian_str_test.h)
target_link_libraries(endian_str_test nbt++)
CXXTEST_ADD_TEST(read_test read_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/read_test.h)
target_link_libraries(read_test nbt++)
add_custom_command(TARGET read_test POST_BUILD
COMMAND ${CMAKE_COMMAND} -E
copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/testfiles ${CMAKE_CURRENT_BINARY_DIR})
CXXTEST_ADD_TEST(write_test write_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/write_test.h)
target_link_libraries(write_test nbt++)
add_executable(format_test format_test.cpp)
target_link_libraries(format_test nbt++)
add_test(format_test format_test)

View File

@ -0,0 +1,175 @@
/*
* libnbt++ - A library for the Minecraft Named Binary Tag format.
* Copyright (C) 2013, 2015 ljfa-ag
*
* This file is part of libnbt++.
*
* libnbt++ is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libnbt++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libnbt++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cxxtest/TestSuite.h>
#include "endian_str.h"
#include <cstdlib>
#include <iostream>
#include <sstream>
using namespace endian;
class endian_str_test : public CxxTest::TestSuite
{
public:
void test_uint()
{
std::stringstream str(std::ios::in | std::ios::out | std::ios::binary);
write_little(str, uint8_t (0x01));
write_little(str, uint16_t(0x0102));
write (str, uint32_t(0x01020304), endian::little);
write_little(str, uint64_t(0x0102030405060708));
write_big (str, uint8_t (0x09));
write_big (str, uint16_t(0x090A));
write_big (str, uint32_t(0x090A0B0C));
write (str, uint64_t(0x090A0B0C0D0E0F10), endian::big);
std::string expected{
1,
2, 1,
4, 3, 2, 1,
8, 7, 6, 5, 4, 3, 2, 1,
9,
9, 10,
9, 10, 11, 12,
9, 10, 11, 12, 13, 14, 15, 16
};
TS_ASSERT_EQUALS(str.str(), expected);
uint8_t u8;
uint16_t u16;
uint32_t u32;
uint64_t u64;
read_little(str, u8);
TS_ASSERT_EQUALS(u8, 0x01);
read_little(str, u16);
TS_ASSERT_EQUALS(u16, 0x0102);
read_little(str, u32);
TS_ASSERT_EQUALS(u32, 0x01020304u);
read(str, u64, endian::little);
TS_ASSERT_EQUALS(u64, 0x0102030405060708u);
read_big(str, u8);
TS_ASSERT_EQUALS(u8, 0x09);
read_big(str, u16);
TS_ASSERT_EQUALS(u16, 0x090A);
read(str, u32, endian::big);
TS_ASSERT_EQUALS(u32, 0x090A0B0Cu);
read_big(str, u64);
TS_ASSERT_EQUALS(u64, 0x090A0B0C0D0E0F10u);
TS_ASSERT(str); //Check if stream has failed
}
void test_sint()
{
std::stringstream str(std::ios::in | std::ios::out | std::ios::binary);
write_little(str, int8_t (-0x01));
write_little(str, int16_t(-0x0102));
write_little(str, int32_t(-0x01020304));
write (str, int64_t(-0x0102030405060708), endian::little);
write_big (str, int8_t (-0x09));
write_big (str, int16_t(-0x090A));
write (str, int32_t(-0x090A0B0C), endian::big);
write_big (str, int64_t(-0x090A0B0C0D0E0F10));
std::string expected{ //meh, stupid narrowing conversions
'\xFF',
'\xFE', '\xFE',
'\xFC', '\xFC', '\xFD', '\xFE',
'\xF8', '\xF8', '\xF9', '\xFA', '\xFB', '\xFC', '\xFD', '\xFE',
'\xF7',
'\xF6', '\xF6',
'\xF6', '\xF5', '\xF4', '\xF4',
'\xF6', '\xF5', '\xF4', '\xF3', '\xF2', '\xF1', '\xF0', '\xF0'
};
TS_ASSERT_EQUALS(str.str(), expected);
int8_t i8;
int16_t i16;
int32_t i32;
int64_t i64;
read_little(str, i8);
TS_ASSERT_EQUALS(i8, -0x01);
read_little(str, i16);
TS_ASSERT_EQUALS(i16, -0x0102);
read(str, i32, endian::little);
TS_ASSERT_EQUALS(i32, -0x01020304);
read_little(str, i64);
TS_ASSERT_EQUALS(i64, -0x0102030405060708);
read_big(str, i8);
TS_ASSERT_EQUALS(i8, -0x09);
read_big(str, i16);
TS_ASSERT_EQUALS(i16, -0x090A);
read_big(str, i32);
TS_ASSERT_EQUALS(i32, -0x090A0B0C);
read(str, i64, endian::big);
TS_ASSERT_EQUALS(i64, -0x090A0B0C0D0E0F10);
TS_ASSERT(str); //Check if stream has failed
}
void test_float()
{
std::stringstream str(std::ios::in | std::ios::out | std::ios::binary);
//C99 has hexadecimal floating point literals, C++ doesn't...
const float fconst = std::stof("-0xCDEF01p-63"); //-1.46325e-012
const double dconst = std::stod("-0x1DEF0102030405p-375"); //-1.09484e-097
//We will be assuming IEEE 754 here
write_little(str, fconst);
write_little(str, dconst);
write_big (str, fconst);
write_big (str, dconst);
std::string expected{
'\x01', '\xEF', '\xCD', '\xAB',
'\x05', '\x04', '\x03', '\x02', '\x01', '\xEF', '\xCD', '\xAB',
'\xAB', '\xCD', '\xEF', '\x01',
'\xAB', '\xCD', '\xEF', '\x01', '\x02', '\x03', '\x04', '\x05'
};
TS_ASSERT_EQUALS(str.str(), expected);
float f;
double d;
read_little(str, f);
TS_ASSERT_EQUALS(f, fconst);
read_little(str, d);
TS_ASSERT_EQUALS(d, dconst);
read_big(str, f);
TS_ASSERT_EQUALS(f, fconst);
read_big(str, d);
TS_ASSERT_EQUALS(d, dconst);
TS_ASSERT(str); //Check if stream has failed
}
};

View File

@ -0,0 +1,81 @@
/*
* libnbt++ - A library for the Minecraft Named Binary Tag format.
* Copyright (C) 2013, 2015 ljfa-ag
*
* This file is part of libnbt++.
*
* libnbt++ is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libnbt++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libnbt++. If not, see <http://www.gnu.org/licenses/>.
*/
//#include "text/json_formatter.h"
//#include "io/stream_reader.h"
#include <fstream>
#include <iostream>
#include <limits>
#include "nbt_tags.h"
using namespace nbt;
int main()
{
//TODO: Write that into a file
tag_compound comp{
{"byte", tag_byte(-128)},
{"short", tag_short(-32768)},
{"int", tag_int(-2147483648)},
{"long", tag_long(-9223372036854775808U)},
{"float 1", 1.618034f},
{"float 2", 6.626070e-34f},
{"float 3", 2.273737e+29f},
{"float 4", -std::numeric_limits<float>::infinity()},
{"float 5", std::numeric_limits<float>::quiet_NaN()},
{"double 1", 3.141592653589793},
{"double 2", 1.749899444387479e-193},
{"double 3", 2.850825855152578e+175},
{"double 4", -std::numeric_limits<double>::infinity()},
{"double 5", std::numeric_limits<double>::quiet_NaN()},
{"string 1", "Hello World! \u00E4\u00F6\u00FC\u00DF"},
{"string 2", "String with\nline breaks\tand tabs"},
{"byte array", tag_byte_array{12, 13, 14, 15, 16}},
{"int array", tag_int_array{0x0badc0de, -0x0dedbeef, 0x1badbabe}},
{"list (empty)", tag_list::of<tag_byte_array>({})},
{"list (float)", tag_list{2.0f, 1.0f, 0.5f, 0.25f}},
{"list (list)", tag_list::of<tag_list>({
{},
{4, 5, 6},
{tag_compound{{"egg", "ham"}}, tag_compound{{"foo", "bar"}}}
})},
{"list (compound)", tag_list::of<tag_compound>({
{{"created-on", 42}, {"names", tag_list{"Compound", "tag", "#0"}}},
{{"created-on", 45}, {"names", tag_list{"Compound", "tag", "#1"}}}
})},
{"compound (empty)", tag_compound()},
{"compound (nested)", tag_compound{
{"key", "value"},
{"key with \u00E4\u00F6\u00FC", tag_byte(-1)},
{"key with\nnewline and\ttab", tag_compound{}}
}},
{"null", nullptr}
};
std::cout << "----- default operator<<:\n";
std::cout << comp;
std::cout << "\n-----" << std::endl;
}

View File

@ -0,0 +1,476 @@
/*
* libnbt++ - A library for the Minecraft Named Binary Tag format.
* Copyright (C) 2013, 2015 ljfa-ag
*
* This file is part of libnbt++.
*
* libnbt++ is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libnbt++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libnbt++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cxxtest/TestSuite.h>
#include "nbt_tags.h"
#include "nbt_visitor.h"
#include <algorithm>
#include <set>
#include <stdexcept>
using namespace nbt;
class nbttest : public CxxTest::TestSuite
{
public:
void test_tag()
{
TS_ASSERT(!is_valid_type(-1));
TS_ASSERT(!is_valid_type(0));
TS_ASSERT(is_valid_type(0, true));
TS_ASSERT(is_valid_type(1));
TS_ASSERT(is_valid_type(5, false));
TS_ASSERT(is_valid_type(7, true));
TS_ASSERT(is_valid_type(11));
TS_ASSERT(!is_valid_type(12));
//looks like TS_ASSERT_EQUALS can't handle abstract classes...
TS_ASSERT(*tag::create(tag_type::Byte) == tag_byte());
TS_ASSERT_THROWS(tag::create(tag_type::Null), std::invalid_argument);
TS_ASSERT_THROWS(tag::create(tag_type::End), std::invalid_argument);
tag_string tstr("foo");
auto cl = tstr.clone();
TS_ASSERT_EQUALS(tstr.get(), "foo");
TS_ASSERT(tstr == *cl);
cl = std::move(tstr).clone();
TS_ASSERT(*cl == tag_string("foo"));
TS_ASSERT(*cl != tag_string("bar"));
cl = std::move(*cl).move_clone();
TS_ASSERT(*cl == tag_string("foo"));
tstr.assign(tag_string("bar"));
TS_ASSERT_THROWS(tstr.assign(tag_int(6)), std::bad_cast);
TS_ASSERT_EQUALS(tstr.get(), "bar");
TS_ASSERT_EQUALS(&tstr.as<tag_string>(), &tstr);
TS_ASSERT_THROWS(tstr.as<tag_byte_array>(), std::bad_cast);
}
void test_get_type()
{
TS_ASSERT_EQUALS(tag_byte().get_type() , tag_type::Byte);
TS_ASSERT_EQUALS(tag_short().get_type() , tag_type::Short);
TS_ASSERT_EQUALS(tag_int().get_type() , tag_type::Int);
TS_ASSERT_EQUALS(tag_long().get_type() , tag_type::Long);
TS_ASSERT_EQUALS(tag_float().get_type() , tag_type::Float);
TS_ASSERT_EQUALS(tag_double().get_type() , tag_type::Double);
TS_ASSERT_EQUALS(tag_byte_array().get_type(), tag_type::Byte_Array);
TS_ASSERT_EQUALS(tag_string().get_type() , tag_type::String);
TS_ASSERT_EQUALS(tag_list().get_type() , tag_type::List);
TS_ASSERT_EQUALS(tag_compound().get_type() , tag_type::Compound);
TS_ASSERT_EQUALS(tag_int_array().get_type() , tag_type::Int_Array);
}
void test_tag_primitive()
{
tag_int tag(6);
TS_ASSERT_EQUALS(tag.get(), 6);
int& ref = tag;
ref = 12;
TS_ASSERT(tag == 12);
TS_ASSERT(tag != 6);
tag.set(24);
TS_ASSERT_EQUALS(ref, 24);
tag = 7;
TS_ASSERT_EQUALS(static_cast<int>(tag), 7);
TS_ASSERT_EQUALS(tag, tag_int(7));
TS_ASSERT_DIFFERS(tag_float(2.5), tag_float(-2.5));
TS_ASSERT_DIFFERS(tag_float(2.5), tag_double(2.5));
TS_ASSERT(tag_double() == 0.0);
TS_ASSERT_EQUALS(tag_byte(INT8_MAX).get(), INT8_MAX);
TS_ASSERT_EQUALS(tag_byte(INT8_MIN).get(), INT8_MIN);
TS_ASSERT_EQUALS(tag_short(INT16_MAX).get(), INT16_MAX);
TS_ASSERT_EQUALS(tag_short(INT16_MIN).get(), INT16_MIN);
TS_ASSERT_EQUALS(tag_int(INT32_MAX).get(), INT32_MAX);
TS_ASSERT_EQUALS(tag_int(INT32_MIN).get(), INT32_MIN);
TS_ASSERT_EQUALS(tag_long(INT64_MAX).get(), INT64_MAX);
TS_ASSERT_EQUALS(tag_long(INT64_MIN).get(), INT64_MIN);
}
void test_tag_string()
{
tag_string tag("foo");
TS_ASSERT_EQUALS(tag.get(), "foo");
std::string& ref = tag;
ref = "bar";
TS_ASSERT_EQUALS(tag.get(), "bar");
TS_ASSERT_DIFFERS(tag.get(), "foo");
tag.set("baz");
TS_ASSERT_EQUALS(ref, "baz");
tag = "quux";
TS_ASSERT_EQUALS("quux", static_cast<std::string>(tag));
std::string str("foo");
tag = str;
TS_ASSERT_EQUALS(tag.get(),str);
TS_ASSERT_EQUALS(tag_string(str).get(), "foo");
TS_ASSERT_EQUALS(tag_string().get(), "");
}
void test_tag_compound()
{
tag_compound comp{
{"foo", int16_t(12)},
{"bar", "baz"},
{"baz", -2.0},
{"list", tag_list{16, 17}}
};
//Test assignments and conversions, and exceptions on bad conversions
TS_ASSERT_EQUALS(comp["foo"].get_type(), tag_type::Short);
TS_ASSERT_EQUALS(static_cast<int32_t>(comp["foo"]), 12);
TS_ASSERT_EQUALS(static_cast<int16_t>(comp.at("foo")), int16_t(12));
TS_ASSERT(comp["foo"] == tag_short(12));
TS_ASSERT_THROWS(static_cast<int8_t>(comp["foo"]), std::bad_cast);
TS_ASSERT_THROWS(static_cast<std::string>(comp["foo"]), std::bad_cast);
TS_ASSERT_THROWS(comp["foo"] = 32, std::bad_cast);
comp["foo"] = int8_t(32);
TS_ASSERT_EQUALS(static_cast<int16_t>(comp["foo"]), 32);
TS_ASSERT_EQUALS(comp["bar"].get_type(), tag_type::String);
TS_ASSERT_EQUALS(static_cast<std::string>(comp["bar"]), "baz");
TS_ASSERT_THROWS(static_cast<int>(comp["bar"]), std::bad_cast);
TS_ASSERT_THROWS(comp["bar"] = -128, std::bad_cast);
comp["bar"] = "barbaz";
TS_ASSERT_EQUALS(static_cast<std::string>(comp["bar"]), "barbaz");
TS_ASSERT_EQUALS(comp["baz"].get_type(), tag_type::Double);
TS_ASSERT_EQUALS(static_cast<double>(comp["baz"]), -2.0);
TS_ASSERT_THROWS(static_cast<float>(comp["baz"]), std::bad_cast);
//Test nested access
comp["quux"] = tag_compound{{"Hello", "World"}, {"zero", 0}};
TS_ASSERT_EQUALS(comp.at("quux").get_type(), tag_type::Compound);
TS_ASSERT_EQUALS(static_cast<std::string>(comp["quux"].at("Hello")), "World");
TS_ASSERT_EQUALS(static_cast<std::string>(comp["quux"]["Hello"]), "World");
TS_ASSERT(comp["list"][1] == tag_int(17));
TS_ASSERT_THROWS(comp.at("nothing"), std::out_of_range);
//Test equality comparisons
tag_compound comp2{
{"foo", int16_t(32)},
{"bar", "barbaz"},
{"baz", -2.0},
{"quux", tag_compound{{"Hello", "World"}, {"zero", 0}}},
{"list", tag_list{16, 17}}
};
TS_ASSERT(comp == comp2);
TS_ASSERT(comp != dynamic_cast<const tag_compound&>(comp2["quux"].get()));
TS_ASSERT(comp != comp2["quux"]);
TS_ASSERT(dynamic_cast<const tag_compound&>(comp["quux"].get()) == comp2["quux"]);
//Test whether begin() through end() goes through all the keys and their
//values. The order of iteration is irrelevant there.
std::set<std::string> keys{"bar", "baz", "foo", "list", "quux"};
TS_ASSERT_EQUALS(comp2.size(), keys.size());
unsigned int i = 0;
for(const std::pair<const std::string, value>& val: comp2)
{
TS_ASSERT_LESS_THAN(i, comp2.size());
TS_ASSERT(keys.count(val.first));
TS_ASSERT(val.second == comp2[val.first]);
++i;
}
TS_ASSERT_EQUALS(i, comp2.size());
//Test erasing and has_key
TS_ASSERT_EQUALS(comp.erase("nothing"), false);
TS_ASSERT(comp.has_key("quux"));
TS_ASSERT(comp.has_key("quux", tag_type::Compound));
TS_ASSERT(!comp.has_key("quux", tag_type::List));
TS_ASSERT(!comp.has_key("quux", tag_type::Null));
TS_ASSERT_EQUALS(comp.erase("quux"), true);
TS_ASSERT(!comp.has_key("quux"));
TS_ASSERT(!comp.has_key("quux", tag_type::Compound));
TS_ASSERT(!comp.has_key("quux", tag_type::Null));
comp.clear();
TS_ASSERT(comp == tag_compound{});
//Test inserting values
TS_ASSERT_EQUALS(comp.put("abc", tag_double(6.0)).second, true);
TS_ASSERT_EQUALS(comp.put("abc", tag_long(-28)).second, false);
TS_ASSERT_EQUALS(comp.insert("ghi", tag_string("world")).second, true);
TS_ASSERT_EQUALS(comp.insert("abc", tag_string("hello")).second, false);
TS_ASSERT_EQUALS(comp.emplace<tag_string>("def", "ghi").second, true);
TS_ASSERT_EQUALS(comp.emplace<tag_byte>("def", 4).second, false);
TS_ASSERT((comp == tag_compound{
{"abc", tag_long(-28)},
{"def", tag_byte(4)},
{"ghi", tag_string("world")}
}));
}
void test_value()
{
value val1;
value val2(make_unique<tag_int>(42));
value val3(tag_int(42));
TS_ASSERT(!val1 && val2 && val3);
TS_ASSERT(val1 == val1);
TS_ASSERT(val1 != val2);
TS_ASSERT(val2 == val3);
TS_ASSERT(val3 == val3);
value valstr(tag_string("foo"));
TS_ASSERT_EQUALS(static_cast<std::string>(valstr), "foo");
valstr = "bar";
TS_ASSERT_THROWS(valstr = 5, std::bad_cast);
TS_ASSERT_EQUALS(static_cast<std::string>(valstr), "bar");
TS_ASSERT(valstr.as<tag_string>() == "bar");
TS_ASSERT_EQUALS(&valstr.as<tag>(), &valstr.get());
TS_ASSERT_THROWS(valstr.as<tag_float>(), std::bad_cast);
val1 = int64_t(42);
TS_ASSERT(val2 != val1);
TS_ASSERT_THROWS(val2 = int64_t(12), std::bad_cast);
TS_ASSERT_EQUALS(static_cast<int64_t>(val2), 42);
tag_int* ptr = dynamic_cast<tag_int*>(val2.get_ptr().get());
TS_ASSERT(*ptr == 42);
val2 = 52;
TS_ASSERT_EQUALS(static_cast<int32_t>(val2), 52);
TS_ASSERT(*ptr == 52);
TS_ASSERT_THROWS(val1["foo"], std::bad_cast);
TS_ASSERT_THROWS(val1.at("foo"), std::bad_cast);
val3 = 52;
TS_ASSERT(val2 == val3);
TS_ASSERT(val2.get_ptr() != val3.get_ptr());
val3 = std::move(val2);
TS_ASSERT(val3 == tag_int(52));
TS_ASSERT(!val2);
tag_int& tag = dynamic_cast<tag_int&>(val3.get());
TS_ASSERT(tag == tag_int(52));
tag = 21;
TS_ASSERT_EQUALS(static_cast<int32_t>(val3), 21);
val1.set_ptr(std::move(val3.get_ptr()));
TS_ASSERT(val1.as<tag_int>() == 21);
TS_ASSERT_EQUALS(val1.get_type(), tag_type::Int);
TS_ASSERT_EQUALS(val2.get_type(), tag_type::Null);
TS_ASSERT_EQUALS(val3.get_type(), tag_type::Null);
val2 = val1;
val1 = val3;
TS_ASSERT(!val1 && val2 && !val3);
TS_ASSERT(val1.get_ptr() == nullptr);
TS_ASSERT(val2.get() == tag_int(21));
TS_ASSERT(value(val1) == val1);
TS_ASSERT(value(val2) == val2);
val1 = val1;
val2 = val2;
TS_ASSERT(!val1);
TS_ASSERT(val1 == value_initializer(nullptr));
TS_ASSERT(val2 == tag_int(21));
val3 = tag_short(2);
TS_ASSERT_THROWS(val3 = tag_string("foo"), std::bad_cast);
TS_ASSERT(val3.get() == tag_short(2));
val2.set_ptr(make_unique<tag_string>("foo"));
TS_ASSERT(val2 == tag_string("foo"));
}
void test_tag_list()
{
tag_list list;
TS_ASSERT_EQUALS(list.el_type(), tag_type::Null);
TS_ASSERT_THROWS(list.push_back(value(nullptr)), std::invalid_argument);
list.emplace_back<tag_string>("foo");
TS_ASSERT_EQUALS(list.el_type(), tag_type::String);
list.push_back(tag_string("bar"));
TS_ASSERT_THROWS(list.push_back(tag_int(42)), std::invalid_argument);
TS_ASSERT_THROWS(list.emplace_back<tag_compound>(), std::invalid_argument);
TS_ASSERT((list == tag_list{"foo", "bar"}));
TS_ASSERT(list[0] == tag_string("foo"));
TS_ASSERT_EQUALS(static_cast<std::string>(list.at(1)), "bar");
TS_ASSERT_EQUALS(list.size(), 2u);
TS_ASSERT_THROWS(list.at(2), std::out_of_range);
TS_ASSERT_THROWS(list.at(-1), std::out_of_range);
list.set(1, value(tag_string("baz")));
TS_ASSERT_THROWS(list.set(1, value(nullptr)), std::invalid_argument);
TS_ASSERT_THROWS(list.set(1, value(tag_int(-42))), std::invalid_argument);
TS_ASSERT_EQUALS(static_cast<std::string>(list[1]), "baz");
TS_ASSERT_EQUALS(list.size(), 2u);
tag_string values[] = {"foo", "baz"};
TS_ASSERT_EQUALS(list.end() - list.begin(), int(list.size()));
TS_ASSERT(std::equal(list.begin(), list.end(), values));
list.pop_back();
TS_ASSERT(list == tag_list{"foo"});
TS_ASSERT(list == tag_list::of<tag_string>({"foo"}));
TS_ASSERT(tag_list::of<tag_string>({"foo"}) == tag_list{"foo"});
TS_ASSERT((list != tag_list{2, 3, 5, 7}));
list.clear();
TS_ASSERT_EQUALS(list.size(), 0u);
TS_ASSERT_EQUALS(list.el_type(), tag_type::String)
TS_ASSERT_THROWS(list.push_back(tag_short(25)), std::invalid_argument);
TS_ASSERT_THROWS(list.push_back(value(nullptr)), std::invalid_argument);
list.reset();
TS_ASSERT_EQUALS(list.el_type(), tag_type::Null);
list.emplace_back<tag_int>(17);
TS_ASSERT_EQUALS(list.el_type(), tag_type::Int);
list.reset(tag_type::Float);
TS_ASSERT_EQUALS(list.el_type(), tag_type::Float);
list.emplace_back<tag_float>(17.0f);
TS_ASSERT(list == tag_list({17.0f}));
TS_ASSERT(tag_list() != tag_list(tag_type::Int));
TS_ASSERT(tag_list() == tag_list());
TS_ASSERT(tag_list(tag_type::Short) != tag_list(tag_type::Int));
TS_ASSERT(tag_list(tag_type::Short) == tag_list(tag_type::Short));
tag_list short_list = tag_list::of<tag_short>({25, 36});
TS_ASSERT_EQUALS(short_list.el_type(), tag_type::Short);
TS_ASSERT((short_list == tag_list{int16_t(25), int16_t(36)}));
TS_ASSERT((short_list != tag_list{25, 36}));
TS_ASSERT((short_list == tag_list{value(tag_short(25)), value(tag_short(36))}));
TS_ASSERT_THROWS((tag_list{value(tag_byte(4)), value(tag_int(5))}), std::invalid_argument);
TS_ASSERT_THROWS((tag_list{value(nullptr), value(tag_int(6))}), std::invalid_argument);
TS_ASSERT_THROWS((tag_list{value(tag_int(7)), value(tag_int(8)), value(nullptr)}), std::invalid_argument);
TS_ASSERT_EQUALS((tag_list(std::initializer_list<value>{})).el_type(), tag_type::Null);
TS_ASSERT_EQUALS((tag_list{2, 3, 5, 7}).el_type(), tag_type::Int);
}
void test_tag_byte_array()
{
std::vector<int8_t> vec{1, 2, 127, -128};
tag_byte_array arr{1, 2, 127, -128};
TS_ASSERT_EQUALS(arr.size(), 4u);
TS_ASSERT(arr.at(0) == 1 && arr[1] == 2 && arr[2] == 127 && arr.at(3) == -128);
TS_ASSERT_THROWS(arr.at(-1), std::out_of_range);
TS_ASSERT_THROWS(arr.at(4), std::out_of_range);
TS_ASSERT(arr.get() == vec);
TS_ASSERT(arr == tag_byte_array(std::vector<int8_t>(vec)));
arr.push_back(42);
vec.push_back(42);
TS_ASSERT_EQUALS(arr.size(), 5u);
TS_ASSERT_EQUALS(arr.end() - arr.begin(), int(arr.size()));
TS_ASSERT(std::equal(arr.begin(), arr.end(), vec.begin()));
arr.pop_back();
arr.pop_back();
TS_ASSERT_EQUALS(arr.size(), 3u);
TS_ASSERT((arr == tag_byte_array{1, 2, 127}));
TS_ASSERT((arr != tag_int_array{1, 2, 127}));
TS_ASSERT((arr != tag_byte_array{1, 2, -1}));
arr.clear();
TS_ASSERT(arr == tag_byte_array());
}
void test_tag_int_array()
{
std::vector<int32_t> vec{100, 200, INT32_MAX, INT32_MIN};
tag_int_array arr{100, 200, INT32_MAX, INT32_MIN};
TS_ASSERT_EQUALS(arr.size(), 4u);
TS_ASSERT(arr.at(0) == 100 && arr[1] == 200 && arr[2] == INT32_MAX && arr.at(3) == INT32_MIN);
TS_ASSERT_THROWS(arr.at(-1), std::out_of_range);
TS_ASSERT_THROWS(arr.at(4), std::out_of_range);
TS_ASSERT(arr.get() == vec);
TS_ASSERT(arr == tag_int_array(std::vector<int32_t>(vec)));
arr.push_back(42);
vec.push_back(42);
TS_ASSERT_EQUALS(arr.size(), 5u);
TS_ASSERT_EQUALS(arr.end() - arr.begin(), int(arr.size()));
TS_ASSERT(std::equal(arr.begin(), arr.end(), vec.begin()));
arr.pop_back();
arr.pop_back();
TS_ASSERT_EQUALS(arr.size(), 3u);
TS_ASSERT((arr == tag_int_array{100, 200, INT32_MAX}));
TS_ASSERT((arr != tag_int_array{100, -56, -1}));
arr.clear();
TS_ASSERT(arr == tag_int_array());
}
void test_visitor()
{
struct : public nbt_visitor
{
tag_type visited = tag_type::Null;
void visit(tag_byte& tag) { visited = tag_type::Byte; }
void visit(tag_short& tag) { visited = tag_type::Short; }
void visit(tag_int& tag) { visited = tag_type::Int; }
void visit(tag_long& tag) { visited = tag_type::Long; }
void visit(tag_float& tag) { visited = tag_type::Float; }
void visit(tag_double& tag) { visited = tag_type::Double; }
void visit(tag_byte_array& tag) { visited = tag_type::Byte_Array; }
void visit(tag_string& tag) { visited = tag_type::String; }
void visit(tag_list& tag) { visited = tag_type::List; }
void visit(tag_compound& tag) { visited = tag_type::Compound; }
void visit(tag_int_array& tag) { visited = tag_type::Int_Array; }
} v;
tag_byte().accept(v);
TS_ASSERT_EQUALS(v.visited, tag_type::Byte);
tag_short().accept(v);
TS_ASSERT_EQUALS(v.visited, tag_type::Short);
tag_int().accept(v);
TS_ASSERT_EQUALS(v.visited, tag_type::Int);
tag_long().accept(v);
TS_ASSERT_EQUALS(v.visited, tag_type::Long);
tag_float().accept(v);
TS_ASSERT_EQUALS(v.visited, tag_type::Float);
tag_double().accept(v);
TS_ASSERT_EQUALS(v.visited, tag_type::Double);
tag_byte_array().accept(v);
TS_ASSERT_EQUALS(v.visited, tag_type::Byte_Array);
tag_string().accept(v);
TS_ASSERT_EQUALS(v.visited, tag_type::String);
tag_list().accept(v);
TS_ASSERT_EQUALS(v.visited, tag_type::List);
tag_compound().accept(v);
TS_ASSERT_EQUALS(v.visited, tag_type::Compound);
tag_int_array().accept(v);
TS_ASSERT_EQUALS(v.visited, tag_type::Int_Array);
}
};

View File

@ -0,0 +1,216 @@
/*
* libnbt++ - A library for the Minecraft Named Binary Tag format.
* Copyright (C) 2013, 2015 ljfa-ag
*
* This file is part of libnbt++.
*
* libnbt++ is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libnbt++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libnbt++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cxxtest/TestSuite.h>
#include "io/stream_reader.h"
#include "nbt_tags.h"
#include <iostream>
#include <fstream>
#include <sstream>
using namespace nbt;
class read_test : public CxxTest::TestSuite
{
public:
void test_stream_reader_big()
{
std::string input{
1, //tag_type::Byte
0, //tag_type::End
11, //tag_type::Int_Array
0x0a, 0x0b, 0x0c, 0x0d, //0x0a0b0c0d in Big Endian
0x00, 0x06, //String length in Big Endian
'f', 'o', 'o', 'b', 'a', 'r',
0 //tag_type::End (invalid with allow_end = false)
};
std::istringstream is(input);
nbt::io::stream_reader reader(is);
TS_ASSERT_EQUALS(&reader.get_istr(), &is);
TS_ASSERT_EQUALS(reader.get_endian(), endian::big);
TS_ASSERT_EQUALS(reader.read_type(), tag_type::Byte);
TS_ASSERT_EQUALS(reader.read_type(true), tag_type::End);
TS_ASSERT_EQUALS(reader.read_type(false), tag_type::Int_Array);
int32_t i;
reader.read_num(i);
TS_ASSERT_EQUALS(i, 0x0a0b0c0d);
TS_ASSERT_EQUALS(reader.read_string(), "foobar");
TS_ASSERT_THROWS(reader.read_type(false), io::input_error);
TS_ASSERT(!is);
is.clear();
//Test for invalid tag type 12
is.str("\x0c");
TS_ASSERT_THROWS(reader.read_type(), io::input_error);
TS_ASSERT(!is);
is.clear();
//Test for unexpcted EOF on numbers (input too short for int32_t)
is.str("\x03\x04");
reader.read_num(i);
TS_ASSERT(!is);
}
void test_stream_reader_little()
{
std::string input{
0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, //0x0d0c0b0a09080706 in Little Endian
0x06, 0x00, //String length in Little Endian
'f', 'o', 'o', 'b', 'a', 'r',
0x10, 0x00, //String length (intentionally too large)
'a', 'b', 'c', 'd' //unexpected EOF
};
std::istringstream is(input);
nbt::io::stream_reader reader(is, endian::little);
TS_ASSERT_EQUALS(reader.get_endian(), endian::little);
int64_t i;
reader.read_num(i);
TS_ASSERT_EQUALS(i, 0x0d0c0b0a09080706);
TS_ASSERT_EQUALS(reader.read_string(), "foobar");
TS_ASSERT_THROWS(reader.read_string(), io::input_error);
TS_ASSERT(!is);
}
//Tests if comp equals an extended variant of Notch's bigtest NBT
void verify_bigtest_structure(const tag_compound& comp)
{
TS_ASSERT_EQUALS(comp.size(), 13u);
TS_ASSERT(comp.at("byteTest") == tag_byte(127));
TS_ASSERT(comp.at("shortTest") == tag_short(32767));
TS_ASSERT(comp.at("intTest") == tag_int(2147483647));
TS_ASSERT(comp.at("longTest") == tag_long(9223372036854775807));
TS_ASSERT(comp.at("floatTest") == tag_float(std::stof("0xff1832p-25"))); //0.4982315
TS_ASSERT(comp.at("doubleTest") == tag_double(std::stod("0x1f8f6bbbff6a5ep-54"))); //0.493128713218231
//From bigtest.nbt: "the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...)"
tag_byte_array byteArrayTest;
for(int n = 0; n < 1000; ++n)
byteArrayTest.push_back((n*n*255 + n*7) % 100);
TS_ASSERT(comp.at("byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))") == byteArrayTest);
TS_ASSERT(comp.at("stringTest") == tag_string("HELLO WORLD THIS IS A TEST STRING \u00C5\u00C4\u00D6!"));
TS_ASSERT(comp.at("listTest (compound)") == tag_list::of<tag_compound>({
{{"created-on", tag_long(1264099775885)}, {"name", "Compound tag #0"}},
{{"created-on", tag_long(1264099775885)}, {"name", "Compound tag #1"}}
}));
TS_ASSERT(comp.at("listTest (long)") == tag_list::of<tag_long>({11, 12, 13, 14, 15}));
TS_ASSERT(comp.at("listTest (end)") == tag_list());
TS_ASSERT((comp.at("nested compound test") == tag_compound{
{"egg", tag_compound{{"value", 0.5f}, {"name", "Eggbert"}}},
{"ham", tag_compound{{"value", 0.75f}, {"name", "Hampus"}}}
}));
TS_ASSERT(comp.at("intArrayTest") == tag_int_array(
{0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f}));
}
void test_read_bigtest()
{
//Uses an extended variant of Notch's original bigtest file
std::ifstream file("bigtest_uncompr", std::ios::binary);
TS_ASSERT(file);
auto pair = nbt::io::read_compound(file);
TS_ASSERT_EQUALS(pair.first, "Level");
verify_bigtest_structure(*pair.second);
}
void test_read_littletest()
{
//Same as bigtest, but little endian
std::ifstream file("littletest_uncompr", std::ios::binary);
TS_ASSERT(file);
auto pair = nbt::io::read_compound(file, endian::little);
TS_ASSERT_EQUALS(pair.first, "Level");
TS_ASSERT_EQUALS(pair.second->get_type(), tag_type::Compound);
verify_bigtest_structure(*pair.second);
}
void test_read_errors()
{
std::ifstream file;
nbt::io::stream_reader reader(file);
//EOF within a tag_double payload
file.open("errortest_eof1", std::ios::binary);
TS_ASSERT(file);
TS_ASSERT_THROWS(reader.read_tag(), io::input_error);
TS_ASSERT(!file);
//EOF within a key in a compound
file.close();
file.open("errortest_eof2", std::ios::binary);
TS_ASSERT(file);
TS_ASSERT_THROWS(reader.read_tag(), io::input_error);
TS_ASSERT(!file);
//Missing tag_end
file.close();
file.open("errortest_noend", std::ios::binary);
TS_ASSERT(file);
TS_ASSERT_THROWS(reader.read_tag(), io::input_error);
TS_ASSERT(!file);
//Negative list length
file.close();
file.open("errortest_neg_length", std::ios::binary);
TS_ASSERT(file);
TS_ASSERT_THROWS(reader.read_tag(), io::input_error);
TS_ASSERT(!file);
}
void test_read_misc()
{
std::ifstream file;
nbt::io::stream_reader reader(file);
//Toplevel tag other than compound
file.open("toplevel_string", std::ios::binary);
TS_ASSERT(file);
TS_ASSERT_THROWS(reader.read_compound(), io::input_error);
TS_ASSERT(!file);
//Rewind and try again with read_tag
file.clear();
TS_ASSERT(file.seekg(0));
auto pair = reader.read_tag();
TS_ASSERT_EQUALS(pair.first, "Test (toplevel tag_string)");
TS_ASSERT(*pair.second == tag_string(
"Even though unprovided for by NBT, the library should also handle "
"the case where the file consists of something else than tag_compound"));
}
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,248 @@
/*
* libnbt++ - A library for the Minecraft Named Binary Tag format.
* Copyright (C) 2013, 2015 ljfa-ag
*
* This file is part of libnbt++.
*
* libnbt++ is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libnbt++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libnbt++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cxxtest/TestSuite.h>
#include "io/stream_writer.h"
#include "io/stream_reader.h"
#include "nbt_tags.h"
#include <iostream>
#include <fstream>
#include <sstream>
using namespace nbt;
class read_test : public CxxTest::TestSuite
{
public:
void test_stream_writer_big()
{
std::ostringstream os;
nbt::io::stream_writer writer(os);
TS_ASSERT_EQUALS(&writer.get_ostr(), &os);
TS_ASSERT_EQUALS(writer.get_endian(), endian::big);
writer.write_type(tag_type::End);
writer.write_type(tag_type::Long);
writer.write_type(tag_type::Int_Array);
writer.write_num(int64_t(0x0102030405060708));
writer.write_string("foobar");
TS_ASSERT(os);
std::string expected{
0, //tag_type::End
4, //tag_type::Long
11, //tag_type::Int_Array
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, //0x0102030405060708 in Big Endian
0x00, 0x06, //string length in Big Endian
'f', 'o', 'o', 'b', 'a', 'r'
};
TS_ASSERT_EQUALS(os.str(), expected);
//too long for NBT
TS_ASSERT_THROWS(writer.write_string(std::string(65536, '.')), std::length_error);
TS_ASSERT(!os);
}
void test_stream_writer_little()
{
std::ostringstream os;
nbt::io::stream_writer writer(os, endian::little);
TS_ASSERT_EQUALS(writer.get_endian(), endian::little);
writer.write_num(int32_t(0x0a0b0c0d));
writer.write_string("foobar");
TS_ASSERT(os);
std::string expected{
0x0d, 0x0c, 0x0b, 0x0a, //0x0a0b0c0d in Little Endian
0x06, 0x00, //string length in Little Endian
'f', 'o', 'o', 'b', 'a', 'r'
};
TS_ASSERT_EQUALS(os.str(), expected);
TS_ASSERT_THROWS(writer.write_string(std::string(65536, '.')), std::length_error);
TS_ASSERT(!os);
}
void test_write_payload_big()
{
std::ostringstream os;
nbt::io::stream_writer writer(os);
//tag_primitive
writer.write_payload(tag_byte(127));
writer.write_payload(tag_short(32767));
writer.write_payload(tag_int(2147483647));
writer.write_payload(tag_long(9223372036854775807));
//Same values as in endian_str_test
writer.write_payload(tag_float(std::stof("-0xCDEF01p-63")));
writer.write_payload(tag_double(std::stod("-0x1DEF0102030405p-375")));
TS_ASSERT_EQUALS(os.str(), (std::string{
'\x7F',
'\x7F', '\xFF',
'\x7F', '\xFF', '\xFF', '\xFF',
'\x7F', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xAB', '\xCD', '\xEF', '\x01',
'\xAB', '\xCD', '\xEF', '\x01', '\x02', '\x03', '\x04', '\x05'
}));
os.str(""); //clear and reuse the stream
//tag_string
writer.write_payload(tag_string("barbaz"));
TS_ASSERT_EQUALS(os.str(), (std::string{
0x00, 0x06, //string length in Big Endian
'b', 'a', 'r', 'b', 'a', 'z'
}));
TS_ASSERT_THROWS(writer.write_payload(tag_string(std::string(65536, '.'))), std::length_error);
TS_ASSERT(!os);
os.clear();
//tag_byte_array
os.str("");
writer.write_payload(tag_byte_array{0, 1, 127, -128, -127});
TS_ASSERT_EQUALS(os.str(), (std::string{
0x00, 0x00, 0x00, 0x05, //length in Big Endian
0, 1, 127, -128, -127
}));
os.str("");
//tag_int_array
writer.write_payload(tag_int_array{0x01020304, 0x05060708, 0x090a0b0c});
TS_ASSERT_EQUALS(os.str(), (std::string{
0x00, 0x00, 0x00, 0x03, //length in Big Endian
0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c
}));
os.str("");
//tag_list
writer.write_payload(tag_list()); //empty list with undetermined type, should be written as list of tag_end
writer.write_payload(tag_list(tag_type::Int)); //empty list of tag_int
writer.write_payload(tag_list{ //nested list
tag_list::of<tag_short>({0x3456, 0x789a}),
tag_list::of<tag_byte>({0x0a, 0x0b, 0x0c, 0x0d})
});
TS_ASSERT_EQUALS(os.str(), (std::string{
0, //tag_type::End
0x00, 0x00, 0x00, 0x00, //length
3, //tag_type::Int
0x00, 0x00, 0x00, 0x00, //length
9, //tag_type::List
0x00, 0x00, 0x00, 0x02, //length
//list 0
2, //tag_type::Short
0x00, 0x00, 0x00, 0x02, //length
'\x34', '\x56',
'\x78', '\x9a',
//list 1
1, //tag_type::Byte
0x00, 0x00, 0x00, 0x04, //length
0x0a,
0x0b,
0x0c,
0x0d
}));
os.str("");
//tag_compound
/* Testing if writing compounds works properly is problematic because the
order of the tags is not guaranteed. However with only two tags in a
compound we only have two possible orderings.
See below for a more thorough test that uses writing and re-reading. */
writer.write_payload(tag_compound{});
writer.write_payload(tag_compound{
{"foo", "quux"},
{"bar", tag_int(0x789abcde)}
});
std::string endtag{0x00};
std::string subtag1{
8, //tag_type::String
0x00, 0x03, //key length
'f', 'o', 'o',
0x00, 0x04, //string length
'q', 'u', 'u', 'x'
};
std::string subtag2{
3, //tag_type::Int
0x00, 0x03, //key length
'b', 'a', 'r',
'\x78', '\x9A', '\xBC', '\xDE'
};
TS_ASSERT(os.str() == endtag + subtag1 + subtag2 + endtag
|| os.str() == endtag + subtag2 + subtag1 + endtag);
//Now for write_tag:
os.str("");
writer.write_tag("foo", tag_string("quux"));
TS_ASSERT_EQUALS(os.str(), subtag1);
TS_ASSERT(os);
//too long key for NBT
TS_ASSERT_THROWS(writer.write_tag(std::string(65536, '.'), tag_long(-1)), std::length_error);
TS_ASSERT(!os);
}
void test_write_bigtest()
{
/* Like already stated above, because no order is guaranteed for
tag_compound, we cannot simply test it by writing into a stream and directly
comparing the output to a reference value.
Instead, we assume that reading already works correctly and re-read the
written tag.
Smaller-grained tests are already done above. */
std::ifstream file("bigtest_uncompr", std::ios::binary);
const auto orig_pair = io::read_compound(file);
std::stringstream sstr;
//Write into stream in Big Endian
io::write_tag(orig_pair.first, *orig_pair.second, sstr);
TS_ASSERT(sstr);
//Read from stream in Big Endian and compare
auto written_pair = io::read_compound(sstr);
TS_ASSERT_EQUALS(orig_pair.first, written_pair.first);
TS_ASSERT(*orig_pair.second == *written_pair.second);
sstr.str(""); //Reset and reuse stream
//Write into stream in Little Endian
io::write_tag(orig_pair.first, *orig_pair.second, sstr, endian::little);
TS_ASSERT(sstr);
//Read from stream in Little Endian and compare
written_pair = io::read_compound(sstr, endian::little);
TS_ASSERT_EQUALS(orig_pair.first, written_pair.first);
TS_ASSERT(*orig_pair.second == *written_pair.second);
}
};