15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2011 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 51320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "base/files/file_util.h" 62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/files/scoped_temp_dir.h" 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/stl_util.h" 8868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/string_util.h" 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/sessions/session_backend.h" 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "testing/gtest/include/gtest/gtest.h" 115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace { 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)typedef std::vector<SessionCommand*> SessionCommands; 155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)struct TestData { 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) SessionCommand::id_type command_id; 185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::string data; 195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)SessionCommand* CreateCommandFromData(const TestData& data) { 225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) SessionCommand* command = 235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new SessionCommand( 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) data.command_id, 255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) static_cast<SessionCommand::size_type>(data.data.size())); 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!data.data.empty()) 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) memcpy(command->contents(), data.data.c_str(), data.data.size()); 285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return command; 295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} // namespace 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class SessionBackendTest : public testing::Test { 345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) protected: 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) virtual void SetUp() { 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) path_ = temp_dir_.path().Append(FILE_PATH_LITERAL("SessionTestDirs")); 38a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles) base::CreateDirectory(path_); 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) void AssertCommandEqualsData(const TestData& data, SessionCommand* command) { 425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) EXPECT_EQ(data.command_id, command->id()); 435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) EXPECT_EQ(data.data.size(), command->size()); 445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) EXPECT_TRUE( 455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) memcmp(command->contents(), data.data.c_str(), command->size()) == 0); 465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Path used in testing. 492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) base::FilePath path_; 502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) base::ScopedTempDir temp_dir_; 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)TEST_F(SessionBackendTest, SimpleReadWrite) { 545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scoped_refptr<SessionBackend> backend( 555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new SessionBackend(BaseSessionService::SESSION_RESTORE, path_)); 565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) struct TestData data = { 1, "a" }; 575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::vector<SessionCommand*> commands; 585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) commands.push_back(CreateCommandFromData(data)); 595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend->AppendCommands(new SessionCommands(commands), false); 605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) commands.clear(); 615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Read it back in. 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend = NULL; 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend = new SessionBackend(BaseSessionService::SESSION_RESTORE, path_); 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend->ReadLastSessionCommandsImpl(&commands); 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ASSERT_EQ(1U, commands.size()); 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) AssertCommandEqualsData(data, commands[0]); 695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) STLDeleteElements(&commands); 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend = NULL; 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend = new SessionBackend(BaseSessionService::SESSION_RESTORE, path_); 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend->ReadLastSessionCommandsImpl(&commands); 755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ASSERT_EQ(0U, commands.size()); 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Make sure we can delete. 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend->DeleteLastSession(); 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend->ReadLastSessionCommandsImpl(&commands); 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ASSERT_EQ(0U, commands.size()); 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)TEST_F(SessionBackendTest, RandomData) { 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) struct TestData data[] = { 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) { 1, "a" }, 875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) { 2, "ab" }, 885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) { 3, "abc" }, 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) { 4, "abcd" }, 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) { 5, "abcde" }, 915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) { 6, "abcdef" }, 925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) { 7, "abcdefg" }, 935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) { 8, "abcdefgh" }, 945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) { 9, "abcdefghi" }, 955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) { 10, "abcdefghij" }, 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) { 11, "abcdefghijk" }, 975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) { 12, "abcdefghijkl" }, 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) { 13, "abcdefghijklm" }, 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }; 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (size_t i = 0; i < arraysize(data); ++i) { 1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scoped_refptr<SessionBackend> backend( 1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new SessionBackend(BaseSessionService::SESSION_RESTORE, path_)); 1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::vector<SessionCommand*> commands; 1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (i != 0) { 1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Read previous data. 1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend->ReadLastSessionCommandsImpl(&commands); 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ASSERT_EQ(i, commands.size()); 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (std::vector<SessionCommand*>::iterator j = commands.begin(); 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) j != commands.end(); ++j) { 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) AssertCommandEqualsData(data[j - commands.begin()], *j); 1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend->AppendCommands(new SessionCommands(commands), false); 1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) commands.clear(); 1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) commands.push_back(CreateCommandFromData(data[i])); 1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend->AppendCommands(new SessionCommands(commands), false); 1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)TEST_F(SessionBackendTest, BigData) { 1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) struct TestData data[] = { 1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) { 1, "a" }, 1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) { 2, "ab" }, 1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }; 1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scoped_refptr<SessionBackend> backend( 1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new SessionBackend(BaseSessionService::SESSION_RESTORE, path_)); 1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::vector<SessionCommand*> commands; 1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) commands.push_back(CreateCommandFromData(data[0])); 1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const SessionCommand::size_type big_size = 1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) SessionBackend::kFileReadBufferSize + 100; 1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const SessionCommand::id_type big_id = 50; 1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) SessionCommand* big_command = new SessionCommand(big_id, big_size); 1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) reinterpret_cast<char*>(big_command->contents())[0] = 'a'; 1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) reinterpret_cast<char*>(big_command->contents())[big_size - 1] = 'z'; 1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) commands.push_back(big_command); 1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) commands.push_back(CreateCommandFromData(data[1])); 1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend->AppendCommands(new SessionCommands(commands), false); 1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) commands.clear(); 1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend = NULL; 1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend = new SessionBackend(BaseSessionService::SESSION_RESTORE, path_); 1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) commands.clear(); 1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend->ReadLastSessionCommandsImpl(&commands); 1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ASSERT_EQ(3U, commands.size()); 1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) AssertCommandEqualsData(data[0], commands[0]); 1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) AssertCommandEqualsData(data[1], commands[2]); 1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) EXPECT_EQ(big_id, commands[1]->id()); 1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ASSERT_EQ(big_size, commands[1]->size()); 1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) EXPECT_EQ('a', reinterpret_cast<char*>(commands[1]->contents())[0]); 1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) EXPECT_EQ('z', 1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) reinterpret_cast<char*>(commands[1]->contents())[big_size - 1]); 1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) STLDeleteElements(&commands); 1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1584e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)TEST_F(SessionBackendTest, EmptyCommand) { 1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) TestData empty_command; 1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) empty_command.command_id = 1; 1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scoped_refptr<SessionBackend> backend( 1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new SessionBackend(BaseSessionService::SESSION_RESTORE, path_)); 1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::vector<SessionCommand*>* empty_commands = 1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new std::vector<SessionCommand*>(); 1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) empty_commands->push_back(CreateCommandFromData(empty_command)); 1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend->AppendCommands(empty_commands, true); 1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend->MoveCurrentSessionToLastSession(); 1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::vector<SessionCommand*> commands; 1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend->ReadLastSessionCommandsImpl(&commands); 1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ASSERT_EQ(1U, commands.size()); 1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) AssertCommandEqualsData(empty_command, commands[0]); 1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) STLDeleteElements(&commands); 1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Writes a command, appends another command with reset to true, then reads 1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// making sure we only get back the second command. 1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)TEST_F(SessionBackendTest, Truncate) { 1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scoped_refptr<SessionBackend> backend( 1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new SessionBackend(BaseSessionService::SESSION_RESTORE, path_)); 1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) struct TestData first_data = { 1, "a" }; 1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::vector<SessionCommand*> commands; 1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) commands.push_back(CreateCommandFromData(first_data)); 1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend->AppendCommands(new SessionCommands(commands), false); 1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) commands.clear(); 1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Write another command, this time resetting the file when appending. 1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) struct TestData second_data = { 2, "b" }; 1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) commands.push_back(CreateCommandFromData(second_data)); 1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend->AppendCommands(new SessionCommands(commands), true); 1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) commands.clear(); 1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Read it back in. 1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend = NULL; 1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend = new SessionBackend(BaseSessionService::SESSION_RESTORE, path_); 1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) backend->ReadLastSessionCommandsImpl(&commands); 1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // And make sure we get back the expected data. 1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ASSERT_EQ(1U, commands.size()); 2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) AssertCommandEqualsData(second_data, commands[0]); 2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) STLDeleteElements(&commands); 2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 204