collabora-online/test/UnitCalc.cpp

512 lines
18 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* Copyright the Collabora Online contributors.
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <config.h>
#include <memory>
#include <ostream>
#include <string>
#include <Poco/Exception.h>
#include <Poco/URI.h>
#include <test/lokassert.hpp>
#include <Png.hpp>
#include <Unit.hpp>
#include <helpers.hpp>
#include <kit/Delta.hpp>
#include <net/WebSocketSession.hpp>
namespace
{
double getColRowSize(const std::string& property, const std::string& message, int index,
const std::string& testname)
{
Poco::JSON::Parser parser;
const Poco::Dynamic::Var result = parser.parse(message);
const auto& command = result.extract<Poco::JSON::Object::Ptr>();
std::string text = command->get("commandName").toString();
LOK_ASSERT_EQUAL(std::string(".uno:ViewRowColumnHeaders"), text);
LOK_ASSERT(command->isArray(property));
Poco::JSON::Array::Ptr array = command->getArray(property);
LOK_ASSERT(array->isObject(index));
Poco::SharedPtr<Poco::JSON::Object> item = array->getObject(index);
LOK_ASSERT(item->has("size"));
return item->getValue<double>("size");
}
double getColRowSize(const std::shared_ptr<http::WebSocketSession>& socket, const std::string& item,
int index, const std::string& testname)
{
std::vector<char> response;
response = helpers::getResponseMessage(socket, "commandvalues:", testname);
LOK_ASSERT_MESSAGE("did not receive a commandvalues: message as expected",
!response.empty());
std::vector<char> json(response.begin() + std::string("commandvalues:").length(),
response.end());
json.push_back(0);
return getColRowSize(item, json.data(), index, testname);
}
}
/// Test suite for Calc.
class UnitCalc : public UnitWSD
{
TestResult testCalcEditRendering();
TestResult testCalcRenderAfterNewView51();
TestResult testCalcRenderAfterNewView53();
TestResult testColumnRowResize();
TestResult testOptimalResize();
public:
UnitCalc()
: UnitWSD("UnitCalc")
{
}
void invokeWSDTest() override;
};
UnitBase::TestResult UnitCalc::testCalcEditRendering()
{
Poco::URI uri(helpers::getTestServerURI());
std::shared_ptr<SocketPoll> socketPoll = std::make_shared<SocketPoll>("CalcPoll");
socketPoll->startThread();
std::shared_ptr<http::WebSocketSession> socket =
helpers::loadDocAndGetSession(socketPoll, "calc_render.xls", uri, testname);
helpers::sendTextFrame(socket, "mouse type=buttondown x=5000 y=5 count=1 buttons=1 modifier=0",
testname);
helpers::sendTextFrame(socket, "key type=input char=97 key=0", testname);
helpers::sendTextFrame(socket, "key type=input char=98 key=0", testname);
helpers::sendTextFrame(socket, "key type=input char=99 key=0", testname);
helpers::assertResponseString(socket, "cellformula: abc", testname);
const char* req = "tilecombine nviewid=0 part=0 width=256 height=256 tileposx=3840 tileposy=0 "
"tilewidth=7680 tileheight=7680";
helpers::sendTextFrame(socket, req, testname);
const std::vector<char> tile = helpers::getResponseMessage(socket, "tile:", testname);
TST_LOG("size: " << tile.size());
// Return early for now when on LO >= 5.2.
int major = 0;
int minor = 0;
helpers::getServerVersion(socket, major, minor, testname);
const std::string firstLine = COOLProtocol::getFirstLine(tile);
Blob zimg = std::make_shared<BlobData>(tile.begin() + firstLine.size() + 1, tile.end());
std::fstream outStream("/tmp/res.z", std::ios::out);
outStream.write(zimg->data(), zimg->size());
outStream.close();
Blob img = DeltaGenerator::expand(zimg);
png_uint_32 height = 256;
png_uint_32 width = 256;
png_uint_32 rowBytes = 256 * 4;
LOK_ASSERT_EQUAL(img->size(), (size_t)rowBytes * height);
std::vector<png_bytep> rows;
for (png_uint_32 i = 0; i < height; ++i)
rows.push_back((png_bytep)img->data() + rowBytes * i);
const std::vector<char> exp
= helpers::readDataFromFile("calc_render_0_256x256.3840,0.7680x7680.png");
std::stringstream streamExp;
std::copy(exp.begin(), exp.end(), std::ostream_iterator<char>(streamExp));
png_uint_32 heightExp = 0;
png_uint_32 widthExp = 0;
png_uint_32 rowBytesExp = 0;
std::vector<png_bytep> rowsExp = Png::decodePNG(streamExp, heightExp, widthExp, rowBytesExp);
LOK_ASSERT_EQUAL(heightExp, height);
LOK_ASSERT_EQUAL(widthExp, width);
LOK_ASSERT_EQUAL(rowBytesExp, rowBytes);
for (png_uint_32 itRow = 0; itRow < height; ++itRow)
{
const bool eq = std::equal(rowsExp[itRow], rowsExp[itRow] + rowBytes, rows[itRow]);
if (!eq)
{
// This is a very strict test that breaks often/easily due to slight rendering
// differences. So for now just keep it informative only.
//LOK_ASSERT_MESSAGE("Tile not rendered as expected @ row #" + std::to_string(itRow), eq);
TST_LOG("\nFAILURE: Tile not rendered as expected @ row #" << itRow);
break;
}
}
return TestResult::Ok;
}
/// When a second view is loaded to a Calc doc,
/// the first stops rendering correctly.
/// This only happens at high rows.
UnitBase::TestResult UnitCalc::testCalcRenderAfterNewView51()
{
// Load a doc with the cursor saved at a top row.
std::string documentPath, documentURL;
helpers::getDocumentPathAndURL("empty.ods", documentPath, documentURL, testname);
Poco::URI uri(helpers::getTestServerURI());
std::shared_ptr<SocketPoll> socketPoll = std::make_shared<SocketPoll>("CalcPoll");
socketPoll->startThread();
std::shared_ptr<http::WebSocketSession> socket =
helpers::loadDocAndGetSession(socketPoll, uri, documentURL, testname);
int major = 0;
int minor = 0;
helpers::getServerVersion(socket, major, minor, testname);
if (major != 5 || minor != 1)
{
TST_LOG("Skipping test on incompatible client [" << major << '.' << minor
<< "], expected [5.1].");
return TestResult::Ok;
}
// Page Down until we get to the bottom of the doc.
for (int i = 0; i < 40; ++i)
{
helpers::sendTextFrame(socket, "key type=input char=0 key=1031", testname);
}
// Wait for status due to doc resize.
helpers::assertResponseString(socket, "status:", testname);
const char* req = "tilecombine nviewid=0 part=0 width=256 height=256 tileposx=0 "
"tileposy=253440 tilewidth=3840 tileheight=3840";
// Get tile.
const std::vector<char> tile1
= helpers::getTileAndSave(socket, req, "/tmp/calc_render_51_orig.png", testname);
// Connect second client, which will load at the top.
TST_LOG("Connecting second client.");
std::shared_ptr<http::WebSocketSession> socket2 =
helpers::loadDocAndGetSession(socketPoll, uri, documentURL, testname);
// Up one row on the first view to trigger the bug.
TST_LOG("Up.");
helpers::sendTextFrame(socket, "key type=input char=0 key=1025", testname);
helpers::assertResponseString(socket, "invalidatetiles:", testname); // Up invalidates.
// Get same tile again.
const std::vector<char> tile2
= helpers::getTileAndSave(socket, req, "/tmp/calc_render_51_sec.png", testname);
LOK_ASSERT(tile1 == tile2);
return TestResult::Ok;
}
UnitBase::TestResult UnitCalc::testCalcRenderAfterNewView53()
{
// Load a doc with the cursor saved at a top row.
std::string documentPath, documentURL;
helpers::getDocumentPathAndURL("calc-render.ods", documentPath, documentURL, testname);
Poco::URI uri(helpers::getTestServerURI());
std::shared_ptr<SocketPoll> socketPoll = std::make_shared<SocketPoll>("CalcPoll");
socketPoll->startThread();
std::shared_ptr<http::WebSocketSession> socket =
helpers::loadDocAndGetSession(socketPoll, uri, documentURL, testname);
int major = 0;
int minor = 0;
helpers::getServerVersion(socket, major, minor, testname);
if (major < 5 || minor < 3)
{
TST_LOG("Skipping test on incompatible client [" << major << '.' << minor
<< "], expected [>=5.3].");
return TestResult::Ok;
}
helpers::sendTextFrame(socket, "clientvisiblearea x=750 y=1861 width=20583 height=6997",
testname);
helpers::sendTextFrame(socket, "key type=input char=0 key=1031", testname);
// Get tile.
const char* req = "tilecombine nviewid=0 part=0 width=256 height=256 tileposx=0 "
"tileposy=291840 tilewidth=3840 tileheight=3840 oldwid=0";
const std::vector<char> tile1
= helpers::getTileAndSave(socket, req, "/tmp/calc_render_53_orig.png", testname);
// Connect second client, which will load at the top.
TST_LOG("Connecting second client.");
std::shared_ptr<http::WebSocketSession> socket2 =
helpers::loadDocAndGetSession(socketPoll, uri, documentURL, testname);
TST_LOG("Waiting for cellviewcursor of second on first.");
helpers::assertResponseString(socket, "cellviewcursor:", testname);
// Get same tile again.
const std::vector<char> tile2
= helpers::getTileAndSave(socket, req, "/tmp/calc_render_53_sec.png", testname);
LOK_ASSERT(tile1 == tile2);
// Don't let them go out of scope and disconnect.
socket2->shutdownWS();
socket->shutdownWS();
return TestResult::Ok;
}
UnitBase::TestResult UnitCalc::testColumnRowResize()
{
try
{
std::vector<char> response;
std::string documentPath, documentURL;
double oldHeight, oldWidth;
helpers::getDocumentPathAndURL("setclientpart.ods", documentPath, documentURL, testname);
Poco::URI uri(helpers::getTestServerURI());
std::shared_ptr<SocketPoll> socketPoll = std::make_shared<SocketPoll>("CalcPoll");
socketPoll->startThread();
std::shared_ptr<http::WebSocketSession> socket =
helpers::loadDocAndGetSession(socketPoll, uri, documentURL, testname);
const std::string commandValues = "commandvalues command=.uno:ViewRowColumnHeaders";
helpers::sendTextFrame(socket, commandValues, testname);
response = helpers::getResponseMessage(socket, "commandvalues:", testname);
LOK_ASSERT_MESSAGE("did not receive a commandvalues: message as expected",
!response.empty());
{
std::vector<char> json(response.begin() + std::string("commandvalues:").length(),
response.end());
json.push_back(0);
// get column 2
oldHeight = getColRowSize("rows", json.data(), 1, testname);
// get row 2
oldWidth = getColRowSize("columns", json.data(), 1, testname);
}
// send column width
{
std::ostringstream oss;
Poco::JSON::Object objJSON, objColumn, objWidth;
double newWidth;
// change column 2
objColumn.set("type", "unsigned short");
objColumn.set("value", 2);
objWidth.set("type", "unsigned short");
objWidth.set("value", oldWidth + 100);
objJSON.set("Column", objColumn);
objJSON.set("Width", objWidth);
Poco::JSON::Stringifier::stringify(objJSON, oss);
helpers::sendTextFrame(socket, "uno .uno:ColumnWidth " + oss.str(), testname);
helpers::sendTextFrame(socket, commandValues, testname);
response = helpers::getResponseMessage(socket, "commandvalues:", testname);
LOK_ASSERT_MESSAGE("did not receive a commandvalues: message as expected",
!response.empty());
std::vector<char> json(response.begin() + std::string("commandvalues:").length(),
response.end());
json.push_back(0);
newWidth = getColRowSize("columns", json.data(), 1, testname);
LOK_ASSERT(newWidth > oldWidth);
}
// send row height
{
std::ostringstream oss;
Poco::JSON::Object objJSON, objRow, objHeight;
double newHeight;
// change row 2
objRow.set("type", "unsigned short");
objRow.set("value", 2);
objHeight.set("type", "unsigned short");
objHeight.set("value", oldHeight + 100);
objJSON.set("Row", objRow);
objJSON.set("Height", objHeight);
Poco::JSON::Stringifier::stringify(objJSON, oss);
helpers::sendTextFrame(socket, "uno .uno:RowHeight " + oss.str(), testname);
helpers::sendTextFrame(socket, commandValues, testname);
response = helpers::getResponseMessage(socket, "commandvalues:", testname);
LOK_ASSERT_MESSAGE("did not receive a commandvalues: message as expected",
!response.empty());
std::vector<char> json(response.begin() + std::string("commandvalues:").length(),
response.end());
json.push_back(0);
newHeight = getColRowSize("rows", json.data(), 1, testname);
LOK_ASSERT(newHeight > oldHeight);
}
}
catch (const Poco::Exception& exc)
{
LOK_ASSERT_FAIL(exc.displayText());
}
return TestResult::Ok;
}
UnitBase::TestResult UnitCalc::testOptimalResize()
{
try
{
double newWidth, newHeight;
Poco::JSON::Object objIndex, objSize, objModifier;
// row/column index 0
objIndex.set("type", "unsigned short");
objIndex.set("value", 1);
// size in twips
objSize.set("type", "unsigned short");
objSize.set("value", 3840);
// keyboard modifier
objModifier.set("type", "unsigned short");
objModifier.set("value", 0);
std::string documentPath, documentURL;
helpers::getDocumentPathAndURL("empty.ods", documentPath, documentURL, testname);
Poco::URI uri(helpers::getTestServerURI());
std::shared_ptr<SocketPoll> socketPoll = std::make_shared<SocketPoll>("CalcPoll");
socketPoll->startThread();
std::shared_ptr<http::WebSocketSession> socket =
helpers::loadDocAndGetSession(socketPoll, uri, documentURL, testname);
const std::string commandValues = "commandvalues command=.uno:ViewRowColumnHeaders";
// send new column width
{
std::ostringstream oss;
Poco::JSON::Object objJSON;
objJSON.set("Column", objIndex);
objJSON.set("Width", objSize);
Poco::JSON::Stringifier::stringify(objJSON, oss);
helpers::sendTextFrame(socket, "uno .uno:ColumnWidth " + oss.str(), testname);
helpers::sendTextFrame(socket, commandValues, testname);
newWidth = getColRowSize(socket, "columns", 0, testname);
}
// send new row height
{
std::ostringstream oss;
Poco::JSON::Object objJSON;
objJSON.set("Row", objIndex);
objJSON.set("Height", objSize);
Poco::JSON::Stringifier::stringify(objJSON, oss);
helpers::sendTextFrame(socket, "uno .uno:RowHeight " + oss.str(), testname);
helpers::sendTextFrame(socket, commandValues, testname);
newHeight = getColRowSize(socket, "rows", 0, testname);
}
objIndex.set("value", 0);
// send optimal column width
{
std::ostringstream oss;
Poco::JSON::Object objJSON;
double optimalWidth;
objJSON.set("Col", objIndex);
objJSON.set("Modifier", objModifier);
Poco::JSON::Stringifier::stringify(objJSON, oss);
helpers::sendTextFrame(socket, "uno .uno:SelectColumn " + oss.str(), testname);
helpers::sendTextFrame(socket, "uno .uno:SetOptimalColumnWidthDirect", testname);
helpers::sendTextFrame(socket, commandValues, testname);
optimalWidth = getColRowSize(socket, "columns", 0, testname);
LOK_ASSERT(optimalWidth < newWidth);
}
// send optimal row height
{
Poco::JSON::Object objSelect, objOptHeight, objExtra;
double optimalHeight;
objSelect.set("Row", objIndex);
objSelect.set("Modifier", objModifier);
objExtra.set("type", "unsigned short");
objExtra.set("value", 0);
objOptHeight.set("aExtraHeight", objExtra);
std::ostringstream oss;
Poco::JSON::Stringifier::stringify(objSelect, oss);
helpers::sendTextFrame(socket, "uno .uno:SelectRow " + oss.str(), testname);
oss.str("");
oss.clear();
Poco::JSON::Stringifier::stringify(objOptHeight, oss);
helpers::sendTextFrame(socket, "uno .uno:SetOptimalRowHeight " + oss.str(), testname);
helpers::sendTextFrame(socket, commandValues, testname);
optimalHeight = getColRowSize(socket, "rows", 0, testname);
LOK_ASSERT(optimalHeight < newHeight);
}
}
catch (const Poco::Exception& exc)
{
LOK_ASSERT_FAIL(exc.displayText());
}
return TestResult::Ok;
}
void UnitCalc::invokeWSDTest()
{
UnitBase::TestResult result = testCalcEditRendering();
if (result != TestResult::Ok)
exitTest(result);
result = testCalcRenderAfterNewView51();
if (result != TestResult::Ok)
exitTest(result);
result = testCalcRenderAfterNewView53();
if (result != TestResult::Ok)
exitTest(result);
// FIXME result = testColumnRowResize();
if (result != TestResult::Ok)
exitTest(result);
// FIXME result = testOptimalResize();
if (result != TestResult::Ok)
exitTest(result);
exitTest(TestResult::Ok);
}
UnitBase* unit_create_wsd(void) { return new UnitCalc(); }
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */