Add test fixture.
This commit is contained in:
parent
af6fad72eb
commit
d6d4319e21
37 changed files with 421 additions and 279 deletions
|
@ -64,11 +64,9 @@ unsigned char Lz77Encoder::getSearchBufferItem(unsigned index) const
|
|||
unsigned Lz77Encoder::lookAheadForMatchingChars(unsigned distance)
|
||||
{
|
||||
unsigned length{0};
|
||||
std::cout << "In hit check at distance " << distance << " max buffer index is: " << mMaxLookAheadBufferIndex << std::endl;
|
||||
for(unsigned idx=0; idx<mMaxLookAheadBufferIndex + 1; idx++)
|
||||
{
|
||||
int search_offset = int(distance-1) - idx;
|
||||
std::cout << "Have search offet " << search_offset << std::endl;
|
||||
unsigned char search_char{0};
|
||||
if (search_offset < 0)
|
||||
{
|
||||
|
@ -78,10 +76,7 @@ unsigned Lz77Encoder::lookAheadForMatchingChars(unsigned distance)
|
|||
{
|
||||
search_char = getSearchBufferItem(static_cast<unsigned>(search_offset));
|
||||
}
|
||||
|
||||
unsigned char lookahead_char = mLookaheadBuffer.getItem(idx);
|
||||
|
||||
std::cout << "Checking search char " << static_cast<int>(search_char) << " and lookup char " << static_cast<int>(lookahead_char) << std::endl;
|
||||
if ((lookahead_char != search_char) || (idx == mMaxLookAheadBufferIndex))
|
||||
{
|
||||
if (idx + 1>= mMinLengthMatchSize)
|
||||
|
@ -100,9 +95,7 @@ void Lz77Encoder::lookForMatches(unsigned char searchChar, unsigned& hitLength,
|
|||
{
|
||||
if (mSearchBuffer.getItem(mSearchBuffer.getNumItems() - 1 - idx) == searchChar)
|
||||
{
|
||||
std::cout << "Looking for hits " << std::endl;
|
||||
auto num_hits = lookAheadForMatchingChars(idx + 1);
|
||||
|
||||
if (num_hits > 0 && num_hits >= hitLength)
|
||||
{
|
||||
hitLength = num_hits;
|
||||
|
@ -197,14 +190,10 @@ bool Lz77Encoder::encode()
|
|||
}
|
||||
|
||||
const auto working_byte = mLookaheadBuffer.getItem(0);
|
||||
std::cout << "Working byte is " << static_cast<int>(working_byte) << std::endl;
|
||||
|
||||
unsigned hit_length{0};
|
||||
unsigned hit_distance{0};
|
||||
lookForMatches(working_byte, hit_length, hit_distance);
|
||||
|
||||
std::cout << "Got hit length " << hit_length << " and distance " << hit_distance << std::endl;
|
||||
|
||||
const Hit hit{hit_length, hit_distance, working_byte};
|
||||
mHitBuffer.push_back(hit);
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include "RawTree.h"
|
||||
|
||||
#include "HuffmanFixedCodes.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <queue>
|
||||
#include <tuple>
|
||||
|
@ -40,34 +42,22 @@ void HuffmanEncoder::dumpTree(const RawTree<CountPair>& tree) const
|
|||
dumpNode(tree.getRootNode(), 0);
|
||||
}
|
||||
|
||||
void HuffmanEncoder::encode(const HuffmanEncoder::DataStream& stream)
|
||||
void HuffmanEncoder::encode(const std::vector<unsigned>& counts)
|
||||
{
|
||||
std::unordered_map<unsigned char, unsigned> counts;
|
||||
for (auto c : stream)
|
||||
{
|
||||
counts[c]++;
|
||||
}
|
||||
encode(counts);
|
||||
}
|
||||
|
||||
void HuffmanEncoder::encode(const std::unordered_map<unsigned char, unsigned>& counts)
|
||||
{
|
||||
std::cout << "Counts" << std::endl;
|
||||
for (const auto& data: counts)
|
||||
{
|
||||
std::cout << data.first << " | " << data.second << std::endl;
|
||||
}
|
||||
std::cout << "*******" << std::endl;
|
||||
|
||||
auto cmp = [](RawNode<CountPair>* left, RawNode<CountPair>* right)
|
||||
{
|
||||
return left->getData().second > right->getData().second;
|
||||
};
|
||||
|
||||
std::priority_queue<RawNode<CountPair>*, std::vector<RawNode<CountPair>* >, decltype(cmp)> q(cmp);
|
||||
for (const auto& entry : counts)
|
||||
unsigned offset{0};
|
||||
for (auto count : counts)
|
||||
{
|
||||
q.push(new RawNode<CountPair>(entry));
|
||||
if (count > 0)
|
||||
{
|
||||
q.push(new RawNode<CountPair>({offset, count}));
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
|
||||
while(q.size() > 1)
|
||||
|
@ -99,6 +89,18 @@ void HuffmanEncoder::encode(const std::unordered_map<unsigned char, unsigned>& c
|
|||
std::cout << "********" << std::endl;
|
||||
}
|
||||
|
||||
void HuffmanEncoder::encode(const std::unordered_map<unsigned char, unsigned>& counts)
|
||||
{
|
||||
std::vector<unsigned> just_counts;
|
||||
for (const auto& data: counts)
|
||||
{
|
||||
mSymbolMapping.push_back(data.first);
|
||||
just_counts.push_back(data.second);
|
||||
}
|
||||
|
||||
encode(just_counts);
|
||||
}
|
||||
|
||||
void HuffmanEncoder::setUseFixedCode(bool useFixed)
|
||||
{
|
||||
mUseFixedCode = useFixed;
|
||||
|
@ -129,16 +131,43 @@ std::optional<PrefixCode> HuffmanEncoder::getEndOfStreamValue() const
|
|||
return mLiteralLengthTable.getCodeForSymbol(256);
|
||||
}
|
||||
|
||||
void HuffmanEncoder::initializeTrees()
|
||||
void HuffmanEncoder::initializeTrees(const std::vector<Hit>& hits)
|
||||
{
|
||||
initializeLiteralLengthTable();
|
||||
initializeLiteralLengthTable(hits);
|
||||
}
|
||||
|
||||
void HuffmanEncoder::initializeLiteralLengthTable()
|
||||
void HuffmanEncoder::initializeLiteralLengthTable(const std::vector<Hit>& hits)
|
||||
{
|
||||
if(mUseFixedCode)
|
||||
{
|
||||
mLiteralLengthTable.setInputLengthSequence(HuffmanFixedCodes::getDeflateFixedHuffmanCodes(), false);
|
||||
mLiteralLengthTable.buildPrefixCodes();
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<unsigned> counts(285, 0);
|
||||
counts[256] = 1;
|
||||
for (const auto& hit : hits)
|
||||
{
|
||||
const auto& [length, distance, next_char] = hit;
|
||||
if (length > 0 )
|
||||
{
|
||||
const auto& [code, extra_bits, num_extra_bits] = HuffmanFixedCodes::getCodeForLength(length);
|
||||
counts[code]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
counts[next_char]++;
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned idx=0; idx<counts.size(); idx++)
|
||||
{
|
||||
if (counts[idx]>0)
|
||||
{
|
||||
std::cout << "Count for " << idx << " is " << counts[idx] << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
encode(counts);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "HuffmanFixedCodes.h"
|
||||
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
|
||||
class PrefixCodeGenerator
|
||||
|
@ -21,11 +22,11 @@ public:
|
|||
|
||||
class HuffmanEncoder : public PrefixCodeGenerator
|
||||
{
|
||||
using DataStream = std::vector<unsigned char>;
|
||||
using CountPair = std::pair<unsigned char, unsigned>;
|
||||
using CountPair = std::pair<unsigned, unsigned>;
|
||||
using Hit = std::tuple<unsigned, unsigned, unsigned char>;
|
||||
|
||||
public:
|
||||
void encode(const DataStream& stream);
|
||||
void encode(const std::vector<unsigned>& counts);
|
||||
void encode(const std::unordered_map<unsigned char, unsigned>& counts);
|
||||
|
||||
uint32_t getLengthValue(unsigned length);
|
||||
|
@ -38,18 +39,19 @@ public:
|
|||
|
||||
std::optional<PrefixCode> getEndOfStreamValue() const override;
|
||||
|
||||
void initializeTrees();
|
||||
void initializeTrees(const std::vector<Hit>& hits);
|
||||
|
||||
void setUseFixedCode(bool useFixed);
|
||||
|
||||
private:
|
||||
void initializeLiteralLengthTable();
|
||||
void initializeLiteralLengthTable(const std::vector<Hit>& hits);
|
||||
|
||||
void dumpTree(const RawTree<CountPair>& tree) const;
|
||||
void dumpNode(RawNode<CountPair>* node, unsigned depth) const;
|
||||
|
||||
bool mUseFixedCode{false};
|
||||
bool mTableIsInitialized{false};
|
||||
|
||||
std::vector<unsigned char> mSymbolMapping;
|
||||
HuffmanCodeLengthTable mLiteralLengthTable;
|
||||
HuffmanCodeLengthTable mDistanceTable;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
namespace HuffmanFixedCodes
|
||||
{
|
||||
|
@ -17,4 +18,128 @@ namespace HuffmanFixedCodes
|
|||
}
|
||||
return sequence;
|
||||
}
|
||||
|
||||
inline std::tuple<unsigned, unsigned char, unsigned char> getCodeForLength(unsigned length)
|
||||
{
|
||||
if (length <= 10)
|
||||
{
|
||||
return {length - 3, 0, 0};
|
||||
}
|
||||
|
||||
unsigned base = 2;
|
||||
unsigned last_offset = 10;
|
||||
for (unsigned n = 1; n < 5; n++)
|
||||
{
|
||||
const auto diff = length - last_offset;
|
||||
if (diff <= 4*base)
|
||||
{
|
||||
auto extra = diff/base + diff % base;
|
||||
return {last_offset + diff/base, extra, n};
|
||||
}
|
||||
|
||||
last_offset += 4*n;
|
||||
base = base*2;
|
||||
}
|
||||
return {258, 0, 0};
|
||||
}
|
||||
|
||||
/*
|
||||
inline std::pair<unsigned, unsigned char> getCodeForLength(unsigned length)
|
||||
{
|
||||
if (length <= 10)
|
||||
{
|
||||
return {257 + length - 3, 0};
|
||||
}
|
||||
else if(length <= 18)
|
||||
{
|
||||
auto offset = length - 10;
|
||||
auto extra = offset/2 + offset % 2;
|
||||
return {265 + offset/2, extra};
|
||||
}
|
||||
else if(length <= 34)
|
||||
{
|
||||
auto offset = length - 19;
|
||||
auto extra = offset/4 + offset % 4;
|
||||
return {269 + offset/4, extra};
|
||||
}
|
||||
else if(length <= 66)
|
||||
{
|
||||
auto offset = length - 35;
|
||||
auto extra = offset/8 + offset % 8;
|
||||
return {273 + offset/8, extra};
|
||||
}
|
||||
else if(length <= 114)
|
||||
{
|
||||
auto offset = length - 67;
|
||||
auto extra = offset/16 + offset % 16;
|
||||
return {277 + offset/16, extra};
|
||||
}
|
||||
else if(length <= 257)
|
||||
{
|
||||
auto offset = length - 115;
|
||||
auto extra = offset/32 + offset % 32;
|
||||
return {281 + offset/32, extra};
|
||||
}
|
||||
else
|
||||
{
|
||||
return {258, 0};
|
||||
}
|
||||
}
|
||||
|
||||
inline unsigned getLengthForCode(unsigned symbol, unsigned extra)
|
||||
{
|
||||
if (symbol <= 264)
|
||||
{
|
||||
return 3 + symbol - 257;
|
||||
}
|
||||
else if (symbol <= 268)
|
||||
{
|
||||
return 11 + 2*(symbol - 265) + extra;
|
||||
const auto valid_dist = readNextDistanceSymbol(distance);
|
||||
copyFromBuffer(length, distance);
|
||||
}
|
||||
else if (symbol <= 272)
|
||||
{
|
||||
unsigned char extra{0};
|
||||
mInputStream->readNextNBits(2, extra);
|
||||
|
||||
auto length = 19 + 4*(symbol - 269) + extra;
|
||||
const auto valid_dist = readNextDistanceSymbol(distance);
|
||||
copyFromBuffer(length, distance);
|
||||
}
|
||||
else if (symbol <= 276)
|
||||
{
|
||||
unsigned char extra{0};
|
||||
mInputStream->readNextNBits(3, extra);
|
||||
|
||||
auto length = 35 + 8*(symbol - 273) + extra;
|
||||
const auto valid_dist = readNextDistanceSymbol(distance);
|
||||
copyFromBuffer(length, distance);
|
||||
}
|
||||
else if (symbol <= 280)
|
||||
{
|
||||
unsigned char extra{0};
|
||||
mInputStream->readNextNBits(4, extra);
|
||||
|
||||
auto length = 67 + 16*(symbol - 277) + extra;
|
||||
const auto valid_dist = readNextDistanceSymbol(distance);
|
||||
copyFromBuffer(length, distance);
|
||||
}
|
||||
else if (symbol <= 284)
|
||||
{
|
||||
unsigned char extra{0};
|
||||
mInputStream->readNextNBits(5, extra);
|
||||
|
||||
auto length = 131 + 32*(symbol - 281) + extra;
|
||||
const auto valid_dist = readNextDistanceSymbol(distance);
|
||||
copyFromBuffer(length, distance);
|
||||
}
|
||||
else if (symbol == 285)
|
||||
{
|
||||
auto length = 258;
|
||||
const auto valid_dist = readNextDistanceSymbol(distance);
|
||||
copyFromBuffer(length, distance);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -200,7 +200,6 @@ void PngWriter::write(const std::unique_ptr<Image<unsigned char> >& image)
|
|||
{
|
||||
auto huffman_encoder = std::make_unique<HuffmanEncoder>();
|
||||
huffman_encoder->setUseFixedCode(true);
|
||||
huffman_encoder->initializeTrees();
|
||||
lz77_encoder.setPrefixCodeGenerator(std::move(huffman_encoder));
|
||||
}
|
||||
lz77_encoder.encode();
|
||||
|
|
|
@ -10,16 +10,14 @@ target_include_directories(test_utils PUBLIC
|
|||
list(APPEND TestFiles
|
||||
audio/TestAudioWriter.cpp
|
||||
audio/TestMidiReader.cpp
|
||||
core/TestByteUtils.cpp
|
||||
core/TestBinaryStream.cpp
|
||||
core/TestBitStream.cpp
|
||||
core/TestTomlReader.cpp
|
||||
core/TestDataStructures.cpp
|
||||
compiler/TestLexer.cpp
|
||||
compiler/TestTemplatingEngine.cpp
|
||||
compression/TestStreamCompressor.cpp
|
||||
compression/TestHuffmanStream.cpp
|
||||
compression/TestLz77Encoder.cpp
|
||||
core/TestByteUtils.cpp
|
||||
core/TestBitStream.cpp
|
||||
core/TestTomlReader.cpp
|
||||
core/TestDataStructures.cpp
|
||||
database/TestDatabase.cpp
|
||||
fonts/TestFontReader.cpp
|
||||
graphics/TestRasterizer.cpp
|
||||
|
@ -29,7 +27,8 @@ list(APPEND TestFiles
|
|||
network/TestNetworkManagerServer.cpp
|
||||
publishing/TestPdfWriter.cpp
|
||||
web/TestMarkdownParser.cpp
|
||||
web/TestXmlParser.cpp)
|
||||
web/TestXmlParser.cpp
|
||||
)
|
||||
|
||||
if (${HAS_FFMPEG})
|
||||
list(APPEND TestFiles
|
||||
|
@ -71,16 +70,6 @@ if(UNIX)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
foreach(TestFile ${TestFiles})
|
||||
cmake_path(GET TestFile FILENAME TestFileName)
|
||||
cmake_path(GET TestFileName STEM TestName)
|
||||
add_executable(test_runner test_runner.cpp ${TestFiles})
|
||||
|
||||
add_executable(${TestName} ${TestFile})
|
||||
|
||||
target_link_libraries(${TestName} PUBLIC core compiler compression fonts network image publishing video database geometry audio graphics web client test_utils ${DBUS_LIBRARIES})
|
||||
set_property(TARGET ${TestName} PROPERTY FOLDER test)
|
||||
endforeach()
|
||||
|
||||
|
||||
add_executable(test_runner test_runner.cpp)
|
||||
target_link_libraries(test_runner PUBLIC core fonts network database geometry audio graphics web client)
|
||||
target_link_libraries(test_runner PUBLIC test_utils publishing core compiler fonts network database geometry audio graphics web client)
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
#include "AudioWriter.h"
|
||||
#include "WasapiInterface.h"
|
||||
|
||||
#include "TestCase.h"
|
||||
#include "TestCaseRunner.h"
|
||||
#include "TestFramework.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
@ -13,11 +12,8 @@
|
|||
#endif
|
||||
#include <iostream>
|
||||
|
||||
class TestWriteWav : public TestCase
|
||||
TEST_CASE(TestWriteWav, "audio")
|
||||
{
|
||||
public:
|
||||
bool Run() override
|
||||
{
|
||||
AudioWriter writer;
|
||||
writer.SetPath("test.wav");
|
||||
|
||||
|
@ -25,38 +21,14 @@ public:
|
|||
auto sample = synth.GetSineWave(240, 5);
|
||||
|
||||
writer.Write(sample);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class TestAudioRender : public TestCase
|
||||
TEST_CASE(TestAudioRender, "audio")
|
||||
{
|
||||
public:
|
||||
bool Run() override
|
||||
{
|
||||
|
||||
#ifdef _WIN32
|
||||
WasapiInterface audio_interface;
|
||||
auto device = AudioDevice::Create();
|
||||
audio_interface.Play(device);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
//int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
|
||||
int main()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
#endif
|
||||
std::cout << "into entry point" << std::endl;
|
||||
TestCaseRunner runner;
|
||||
runner.AddTestCase("TestWriteNav", std::make_unique<TestWriteWav>());
|
||||
runner.AddTestCase("TestAudioRender", std::make_unique<TestAudioRender>());
|
||||
|
||||
const auto testsPassed = runner.Run();
|
||||
return testsPassed ? 0 : -1;
|
||||
#ifdef _WIN32
|
||||
CoUninitialize();
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -1,31 +1,16 @@
|
|||
#include "MidiReader.h"
|
||||
|
||||
#include "TestCase.h"
|
||||
#include "TestCaseRunner.h"
|
||||
#include "TestFramework.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
class TestReadMidi : public TestCase
|
||||
TEST_CASE(TestReadMidi, "audio")
|
||||
{
|
||||
public:
|
||||
bool Run() override
|
||||
{
|
||||
MidiReader reader;
|
||||
reader.Read("/home/jmsgrogan/Downloads/test.mid");
|
||||
|
||||
auto document = reader.GetDocument();
|
||||
std::cout << document->Serialize() << std::endl;
|
||||
return true;
|
||||
}
|
||||
// std::cout << document->Serialize() << std::endl;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
TestCaseRunner runner;
|
||||
runner.AddTestCase("TestReadMidi", std::make_unique<TestReadMidi>());
|
||||
|
||||
const auto testsPassed = runner.Run();
|
||||
return testsPassed ? 0 : -1;
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
return 0;
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
#include "TemplatingEngine.h"
|
||||
|
||||
#include <File.h>
|
||||
#include "TestFramework.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
TEST_CASE(TestTemplatingEngine, "compiler")
|
||||
{
|
||||
const auto data_loc = std::filesystem::path(__FILE__) / "../../data";
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
#include <iostream>
|
||||
|
||||
#include "TestFramework.h"
|
||||
|
||||
#include "HuffmanStream.h"
|
||||
#include "BufferBitStream.h"
|
||||
|
||||
void testHuffmanCodeLengthTable()
|
||||
TEST_CASE(TestHuffmanCodeLengthTable, "compression")
|
||||
{
|
||||
HuffmanCodeLengthTable table;
|
||||
|
||||
|
@ -17,23 +19,23 @@ void testHuffmanCodeLengthTable()
|
|||
}
|
||||
}
|
||||
table.setInputLengthSequence(code_length_sequence, false);
|
||||
|
||||
table.buildCompressedLengthSequence();
|
||||
|
||||
auto compressed_sequence = table.getCompressedLengthSequence();
|
||||
for (auto entry : compressed_sequence)
|
||||
{
|
||||
std::cout << "Count " << entry.first << " extra bits " << entry.second << std::endl;
|
||||
// std::cout << "Count " << entry.first << " extra bits " << entry.second << std::endl;
|
||||
}
|
||||
|
||||
auto compressed_lengths = table.getCompressedLengthCounts();
|
||||
for(unsigned idx = 0; idx<compressed_lengths.size(); idx++)
|
||||
{
|
||||
std::cout << "Slot " << idx << " length " << compressed_lengths[idx] << std::endl;
|
||||
//std::cout << "Slot " << idx << " length " << compressed_lengths[idx] << std::endl;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void testLiteralsTable()
|
||||
|
||||
TEST_CASE(TestLiteralsTable, "compression")
|
||||
{
|
||||
std::vector<unsigned char> lengths = {7,4,4,7,5,5,7,7,6,6,7,6,6,6,8,6,6,8,
|
||||
6,6,7,6,8,7,7,7,7,7,7,6,6,7,7,6,6,7,7,8,8,7,7,7,6,6,7,7,7,7,6,7,7,7,
|
||||
|
@ -53,13 +55,13 @@ void testLiteralsTable()
|
|||
auto compressed_sequence = table.getCompressedLengthSequence();
|
||||
for (auto entry : compressed_sequence)
|
||||
{
|
||||
std::cout << "Code " << entry.first << " extra bits " << entry.second << std::endl;
|
||||
// std::cout << "Code " << entry.first << " extra bits " << entry.second << std::endl;
|
||||
}
|
||||
|
||||
auto compressed_lengths = table.getCompressedLengthCounts();
|
||||
for(unsigned idx = 0; idx<compressed_lengths.size(); idx++)
|
||||
{
|
||||
std::cout << "Slot " << idx << " length " << compressed_lengths[idx] << std::endl;
|
||||
// std::cout << "Slot " << idx << " length " << compressed_lengths[idx] << std::endl;
|
||||
}
|
||||
|
||||
HuffmanCodeLengthTable codingTable;
|
||||
|
@ -99,7 +101,6 @@ void testLiteralsTable()
|
|||
{
|
||||
out_stream.writeNBits(permuted[idx], 3);
|
||||
}
|
||||
*/
|
||||
|
||||
for(unsigned idx=0; idx<14;idx++)
|
||||
{
|
||||
|
@ -131,15 +132,15 @@ void testLiteralsTable()
|
|||
std::cout << "Output is: " << std::endl;
|
||||
auto dump = out_stream.logNextNBytes(out_stream.getBuffer().size());
|
||||
std::cout << dump << std::endl;
|
||||
|
||||
}
|
||||
*/
|
||||
};
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
//int main()
|
||||
//{
|
||||
//testHuffmanCodeLengthTable();
|
||||
|
||||
testLiteralsTable();
|
||||
// testLiteralsTable();
|
||||
//HuffmanStream stream(nullptr, nullptr);
|
||||
|
||||
//stream.setCodeLengthAlphabetLengths({3, 3, 3, 3, 3, 2, 4, 4});
|
||||
|
@ -148,7 +149,7 @@ int main()
|
|||
|
||||
//stream.buildCodeLengthMapping();
|
||||
|
||||
std::cout << "*******" << std::endl;
|
||||
//std::cout << "*******" << std::endl;
|
||||
//stream.setCodeLengthAlphabetLengths({4, 0, 6, 7, 3, 2, 4, 2, 7, 4, 6, 3, 0, 6});
|
||||
|
||||
//stream.buildCodeLengthMapping();
|
||||
|
@ -157,6 +158,8 @@ int main()
|
|||
|
||||
//stream.generateFixedCodeMapping();
|
||||
|
||||
// testCodeConversions();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// return 0;
|
||||
//}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
#include <iostream>
|
||||
|
||||
#include "Lz77Encoder.h"
|
||||
#include "HuffmanEncoder.h"
|
||||
#include "BufferBitStream.h"
|
||||
|
||||
int main()
|
||||
#include "TestFramework.h"
|
||||
|
||||
TEST_CASE(TestLz77Encoder, "compression")
|
||||
{
|
||||
std::vector<unsigned> values {0, 10, 11, 12, 10, 11, 12, 0, 13, 14, 15, 10, 11, 12};
|
||||
|
||||
|
@ -28,5 +31,6 @@ int main()
|
|||
std::cout << "Got hit " << length << " | " << distance << " | " << static_cast<int>(next_char) << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
HuffmanEncoder huffman_encoder;
|
||||
huffman_encoder.initializeTrees(hit_buffer);
|
||||
}
|
||||
|
|
|
@ -5,21 +5,25 @@
|
|||
#include "RunLengthEncoder.h"
|
||||
#include "Lz77Encoder.h"
|
||||
|
||||
void test_run_length_encoder()
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include "TestFramework.h"
|
||||
|
||||
TEST_CASE(TestRunLengthEncoder, "compression")
|
||||
{
|
||||
std::string test_data = "BCAAAADDDCCACACAC";
|
||||
|
||||
RunLengthEncoder encoder;
|
||||
auto encoded = encoder.encode(test_data);
|
||||
auto encoded = encoder.encode(StringUtils::toBytes(test_data));
|
||||
|
||||
std::cout << "Encoded: " << encoded << std::endl;
|
||||
//std::cout << "Encoded: " << encoded << std::endl;
|
||||
|
||||
auto decoded = encoder.decode(encoded);
|
||||
|
||||
std::cout << "Decoded: " << decoded << std::endl;
|
||||
//std::cout << "Decoded: " << decoded << std::endl;
|
||||
}
|
||||
|
||||
void test_huffman_encoder()
|
||||
TEST_CASE(TestHuffmanEncoder, "compression")
|
||||
{
|
||||
//std::string testData = "BCAADDDCCACACAC";
|
||||
//std::vector<unsigned char> stream(testData.begin(), testData.end());
|
||||
|
@ -38,7 +42,7 @@ void test_huffman_encoder()
|
|||
encoder.encode(counts);
|
||||
}
|
||||
|
||||
void test_lz77_encoder()
|
||||
TEST_CASE(TestLz77Encoder, "compression")
|
||||
{
|
||||
std::string test_data = "sir sid eastman easily teases sea sick seals";
|
||||
//std::string test_data = "sir sid eastman";
|
||||
|
@ -56,14 +60,3 @@ void test_lz77_encoder()
|
|||
|
||||
//std::cout << "Decoded: " << decoded << std::endl;
|
||||
}
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
//test_huffman_encoder();
|
||||
|
||||
//test_run_length_encoder();
|
||||
|
||||
test_lz77_encoder();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
#include "ByteUtils.h"
|
||||
#include "BufferBitStream.h"
|
||||
|
||||
#include "TestFramework.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
void testReading()
|
||||
TEST_CASE(TestReadBitStream, "core")
|
||||
{
|
||||
std::vector<std::string> bytes{
|
||||
"11101101",
|
||||
|
@ -37,7 +39,7 @@ void testReading()
|
|||
std::cout << "Slice3 is " << ByteUtils::toString(buffer) << std::endl;
|
||||
}
|
||||
|
||||
void testWriting()
|
||||
TEST_CASE(TestWritingBitStream, "core")
|
||||
{
|
||||
BufferBitStream stream;
|
||||
|
||||
|
@ -72,13 +74,3 @@ void testWriting()
|
|||
std::cout << "Got bytes 3 " << byte3 << std::endl;
|
||||
std::cout << "Got bytes 4 " << byte4 << std::endl;
|
||||
}
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
//testReading()
|
||||
|
||||
testWriting();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
#include "ByteUtils.h"
|
||||
|
||||
#include "TestFramework.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
TEST_CASE(TestReadByteUtils, "core")
|
||||
{
|
||||
auto byte = ByteUtils::getFromString("00110101");
|
||||
std::cout << "Value is " << static_cast<unsigned>(byte) << std::endl;
|
||||
|
@ -32,6 +34,4 @@ int main()
|
|||
unsigned hold = byte;
|
||||
hold = (hold << 5) + 3;
|
||||
std::cout << "Big val is " << ByteUtils::toString(hold, 16) << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
#include "CircleBuffer.h"
|
||||
|
||||
#include "TestFramework.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
TEST_CASE(TestCircleBuffer, "core")
|
||||
{
|
||||
CircleBuffer<unsigned> buffer(3);
|
||||
|
||||
|
@ -29,6 +31,4 @@ int main()
|
|||
auto item = buffer.getItem(idx);
|
||||
std::cout << "Got item: " << idx << " " << item << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
#include "TomlReader.h"
|
||||
|
||||
#include "TestFramework.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
TEST_CASE(TestTomlReader, "core")
|
||||
{
|
||||
const auto data_loc = std::filesystem::path(__FILE__) / "../../data";
|
||||
const auto sample_toml_file = data_loc / "sample_toml.toml";
|
||||
|
@ -16,6 +18,4 @@ int main()
|
|||
{
|
||||
std::cout << "Got entry with key: " << items.first << " and val " << items.second << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
#include "DatabaseManager.h"
|
||||
|
||||
int main()
|
||||
#include "TestFramework.h"
|
||||
|
||||
TEST_CASE(TestDatabaseManager, "database")
|
||||
{
|
||||
DatabaseManager db_manager;
|
||||
db_manager.CreateDatabase("test.db");
|
||||
|
||||
std::string statement = "CREATE TABLE corporation;";
|
||||
db_manager.Run(statement);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
#include "TestFramework.h"
|
||||
|
||||
TEST_CASE(TestFontReader, "fonts")
|
||||
{
|
||||
const auto font_path = "/usr/share/fonts/truetype/tlwg/TlwgTypo-Bold.ttf";
|
||||
|
||||
|
@ -12,6 +14,4 @@ int main()
|
|||
auto font = reader.read();
|
||||
|
||||
font->dumpInfo();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
#include "FontGlyph.h"
|
||||
#include "Image.h"
|
||||
|
||||
#include "TestFramework.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
TEST_CASE(TestFreeTypeFontEngine, "fonts")
|
||||
{
|
||||
FreeTypeFontEngine engine;
|
||||
engine.initialize();
|
||||
|
@ -14,6 +16,4 @@ int main()
|
|||
engine.loadGlyph('A');
|
||||
engine.loadGlyph(66);
|
||||
engine.loadGlyph(67);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -3,30 +3,17 @@
|
|||
|
||||
#include "GuiApplication.h"
|
||||
|
||||
#include "TestFramework.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
class TestOpenGlRendering : public TestCase
|
||||
TEST_CASE(TestOpenGlRendering, "graphics")
|
||||
{
|
||||
public:
|
||||
bool Run() override
|
||||
{
|
||||
auto app = std::make_unique<GuiApplication>();
|
||||
|
||||
app->setUiInterfaceBackend(UiInterfaceFactory::Backend::X11);
|
||||
//app->setUiInterfaceBackend(UiInterfaceFactory::Backend::X11_RASTER);
|
||||
app->run();
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
TestCaseRunner runner;
|
||||
runner.AddTestCase("TestOpenGlRendering", std::make_unique<TestOpenGlRendering>());
|
||||
|
||||
const auto testsPassed = runner.Run();
|
||||
return testsPassed ? 0 : -1;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
#include "Point.h"
|
||||
#include "TriMesh.h"
|
||||
|
||||
int main()
|
||||
#include "TestFramework.h"
|
||||
|
||||
TEST_CASE(TestRasterizer, "graphics")
|
||||
{
|
||||
/*
|
||||
DrawingManager manager;
|
||||
|
@ -18,6 +20,4 @@ int main()
|
|||
|
||||
auto line = LineSegment::Create(Point(10.0, 10.0), Point(190.0, 190.0));
|
||||
*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
#include "Image.h"
|
||||
#include <iostream>
|
||||
|
||||
void testThirdParty()
|
||||
#include "TestFramework.h"
|
||||
|
||||
TEST_CASE(TestThirdPartyPng, "image")
|
||||
{
|
||||
const auto path = "/home/jmsgrogan/Downloads/test.png";
|
||||
|
||||
|
@ -26,7 +28,7 @@ void testThirdParty()
|
|||
//}
|
||||
}
|
||||
|
||||
void testFixedCode()
|
||||
TEST_CASE(TestFxedCodePng, "image")
|
||||
{
|
||||
const auto path = "/home/jmsgrogan/code/MediaTool-build/bin/test_fixed.png";
|
||||
|
||||
|
@ -37,11 +39,3 @@ void testFixedCode()
|
|||
reader.setPath(path);
|
||||
auto image = reader.read();
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
testThirdParty();
|
||||
//testFixedCode();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
#include "ByteUtils.h"
|
||||
#include "ImagePrimitives.h"
|
||||
|
||||
#include "TestFramework.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
void testCompressedPng()
|
||||
TEST_CASE(TestCompressedPng, "image")
|
||||
{
|
||||
unsigned width = 20;
|
||||
unsigned height = 20;
|
||||
|
@ -43,7 +45,7 @@ void testCompressedPng()
|
|||
test_file.Close();
|
||||
}
|
||||
|
||||
void testFixedPng()
|
||||
TEST_CASE(TestFixedPng, "image")
|
||||
{
|
||||
unsigned width = 10;
|
||||
unsigned height = 10;
|
||||
|
@ -73,7 +75,7 @@ void testFixedPng()
|
|||
|
||||
}
|
||||
|
||||
void testDynamicCompressedPng()
|
||||
TEST_CASE(TestDynamicCompressedPng, "image")
|
||||
{
|
||||
unsigned width = 10;
|
||||
unsigned height = 10;
|
||||
|
@ -99,14 +101,4 @@ void testDynamicCompressedPng()
|
|||
//return;
|
||||
File test_file("test_dynamic.png");
|
||||
std::cout << test_file.dumpBinary();
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
//testCompressedPng();
|
||||
//testFixedPng();
|
||||
testDynamicCompressedPng();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
#include "NetworkManager.h"
|
||||
|
||||
#include "TestFramework.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
TEST_CASE(TestNetworkManagerClient, "network")
|
||||
{
|
||||
std::cout << "into main" << std::endl;
|
||||
auto network_manager = NetworkManager::Create();
|
||||
|
||||
network_manager->RunHttpClient();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
#include "NetworkManager.h"
|
||||
|
||||
#include "TestFramework.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
TEST_CASE(TestNetworkManagerServer, "network")
|
||||
{
|
||||
std::cout << "into main" << std::endl;
|
||||
auto network_manager = NetworkManager::Create();
|
||||
|
||||
network_manager->RunHttpServer();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
#include "PdfObject.h"
|
||||
#include "File.h"
|
||||
|
||||
int main()
|
||||
#include "TestFramework.h"
|
||||
|
||||
TEST_CASE(TestPdfWriter, "publishing")
|
||||
{
|
||||
auto document = std::make_unique<PdfDocument>();
|
||||
|
||||
|
@ -14,5 +16,4 @@ int main()
|
|||
pdf_file.SetAccessMode(File::AccessMode::Write);
|
||||
pdf_file.Open();
|
||||
pdf_file.WriteText(output);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,21 @@
|
|||
#include "TestFramework.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
//int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
|
||||
int main()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
#endif
|
||||
|
||||
auto test_runner = TestCaseRunner::getInstance();
|
||||
test_runner.run();
|
||||
|
||||
#ifdef _WIN32
|
||||
CoUninitialize();
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
class TestCase
|
||||
{
|
||||
public:
|
||||
TestCase(){};
|
||||
using TestCaseFunction = std::function<void()>;
|
||||
|
||||
TestCase(const std::string& name, const std::string& tags, TestCaseFunction func)
|
||||
: mName(name),
|
||||
mTags(tags),
|
||||
mTestFunction(func)
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
virtual ~TestCase() = default;
|
||||
TestCase(const TestCase&) = delete;
|
||||
virtual bool Run() {return false;};
|
||||
|
||||
const std::string& getName() const
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
const std::string& getTags() const
|
||||
{
|
||||
return mTags;
|
||||
}
|
||||
|
||||
virtual void run()
|
||||
{
|
||||
mTestFunction();
|
||||
};
|
||||
|
||||
public:
|
||||
TestCaseFunction mTestFunction{nullptr};
|
||||
|
||||
private:
|
||||
std::string mName;
|
||||
std::string mTags;
|
||||
|
||||
};
|
||||
using TestCasePtr = std::unique_ptr<TestCase>;
|
||||
|
|
|
@ -3,24 +3,42 @@
|
|||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
void TestCaseRunner::AddTestCase(const std::string& label, TestCasePtr testCase)
|
||||
TestCaseRunner::~TestCaseRunner()
|
||||
{
|
||||
mCases.push_back({label, std::move(testCase)});
|
||||
for (auto testCase : mCases)
|
||||
{
|
||||
//delete testCase;
|
||||
}
|
||||
}
|
||||
|
||||
bool TestCaseRunner::Run()
|
||||
void TestCaseRunner::addTestCase(const std::string& label, const std::string& tag, TestCase::TestCaseFunction func)
|
||||
{
|
||||
auto test_case = new TestCase(label, tag, func);
|
||||
mCases.push_back(test_case);
|
||||
}
|
||||
|
||||
bool TestCaseRunner::run()
|
||||
{
|
||||
for (auto test_case : mCases)
|
||||
{
|
||||
std::cout << "TestFramework: Running Test - " << test_case->getName() << std::endl;
|
||||
test_case->run();
|
||||
}
|
||||
|
||||
bool testsPassed = true;
|
||||
/*
|
||||
for(const auto& test : mCases)
|
||||
{
|
||||
std::cout << "Running " << test.first << std::endl;
|
||||
const auto result = test.second->Run();
|
||||
std::cout << "Running " << test->getName() << std::endl;
|
||||
const auto result = test->Run();
|
||||
if (!result)
|
||||
{
|
||||
std::cout << test.first << " Failed" << std::endl;
|
||||
std::cout << test->getName() << " Failed" << std::endl;
|
||||
testsPassed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return testsPassed;
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -8,12 +8,20 @@
|
|||
class TestCaseRunner
|
||||
{
|
||||
public:
|
||||
TestCaseRunner() = default;
|
||||
|
||||
void AddTestCase(const std::string& label, TestCasePtr testCase);
|
||||
static TestCaseRunner& getInstance()
|
||||
{
|
||||
static TestCaseRunner instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool Run();
|
||||
~TestCaseRunner();
|
||||
|
||||
void addTestCase(const std::string& label, const std::string& tag, TestCase::TestCaseFunction func);
|
||||
|
||||
bool run();
|
||||
|
||||
private:
|
||||
using TestInstance = std::pair<std::string, TestCasePtr>;
|
||||
std::vector<TestInstance> mCases;
|
||||
std::vector<TestCase*> mCases;
|
||||
};
|
||||
|
|
18
test/test_utils/TestFramework.h
Normal file
18
test/test_utils/TestFramework.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include "TestCaseRunner.h"
|
||||
|
||||
struct Holder
|
||||
{
|
||||
Holder(const std::string& name, const std::string& tags, std::function<void()> func)
|
||||
{
|
||||
TestCaseRunner::getInstance().addTestCase(name, tags, func);
|
||||
}
|
||||
};
|
||||
|
||||
#define TEST_CASE(NAME, TAGS) \
|
||||
static void Test##NAME(); \
|
||||
namespace{Holder holder##NAME( #NAME, #TAGS, &Test##NAME );} \
|
||||
static void Test##NAME() \
|
||||
|
||||
|
|
@ -2,10 +2,11 @@
|
|||
#include "Image.h"
|
||||
#include "FfmpegInterface.h"
|
||||
#include "PngWriter.h"
|
||||
#include "PngWriterImpl.h"
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
#include "TestFramework.h"
|
||||
|
||||
TEST_CASE(TestVideoDecoder, "video")
|
||||
{
|
||||
auto video = Video::Create();
|
||||
video->SetPath("/home/jmsgrogan/test.mp4");
|
||||
|
@ -21,6 +22,4 @@ int main()
|
|||
writer.SetPath(filename);
|
||||
writer.Write(images[idx]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
#include "File.h"
|
||||
#include "HtmlWriter.h"
|
||||
|
||||
int main()
|
||||
#include "TestFramework.h"
|
||||
|
||||
TEST_CASE(TestMarkdownParser, "web")
|
||||
{
|
||||
File md_file("/home/jmsgrogan/code/MediaTool/test/data/sample_markdown.md");
|
||||
md_file.Open();
|
||||
|
@ -20,6 +22,4 @@ int main()
|
|||
html_file.SetAccessMode(File::AccessMode::Write);
|
||||
html_file.Open();
|
||||
html_file.WriteText(html_string);
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
#include "XmlWriter.h"
|
||||
#include "File.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
#include "TestFramework.h"
|
||||
|
||||
TEST_CASE(TestXmlParser, "web")
|
||||
{
|
||||
XmlParser parser;
|
||||
|
||||
|
@ -26,6 +28,4 @@ int main(int argc, char *argv[])
|
|||
XmlWriter writer;
|
||||
auto content = writer.ToString(parser.GetDocument().get());
|
||||
outFile->WriteText(content);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
|
||||
#include "GuiApplication.h"
|
||||
|
||||
int main()
|
||||
#include "TestFramework.h"
|
||||
|
||||
TEST_CASE(TestWaylandWindow, "web")
|
||||
{
|
||||
FileLogger::GetInstance().Open();
|
||||
|
||||
|
@ -15,6 +17,4 @@ int main()
|
|||
app->setUiInterfaceBackend(UiInterfaceFactory::Backend::WAYLAND_RASTER);
|
||||
|
||||
app->run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue