7183a3d3de
Change-Id: Ice934380029bf27054e830fffc07a5d037d1430f Signed-off-by: Michael Meeks <michael.meeks@collabora.com>
511 lines
18 KiB
C++
511 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: */
|