论如何给一门课出大作业及评测系统

Introduction

本来是我的一门课的作业。。看到助教给的评测demo以后深受启发,就拿去考小朋友了。

另外友元也的确是一个神奇的存在。。我是本学期过了一半才代替我的室友(他有事)当了这门课的助教。
当时正好讲到友元,我第一次去答疑,顶门来一个人问我“友元blahblahblah”,我都愣了,啥?我混迹开源社区两年了,哪个西方国家的程序员写的代码我没有见过?就是没听说过友元这个东西。。。

然后我就想起了一句经验之谈:每个程序设计语言都有他设计的渣的地方。。一查,果然是java,c#,oc什么的都把这个玩意去了,嗯。。yahaliyahali。。

所以这玩意我就耿耿于怀了半个学期,心想一定要和大家分享一下。。

题目描述

下述两个类用来模拟一个分布式存储系统,

1
2
3
4
5
6
7
8
9
10
//server.h
class Server {
public:
Server() {}
~Server() {}
bool UpdateTable(const std::string &content, const std::string &tableName);
protected:
std::map<size_t, *Worker> mWorkers;
std::map<std::string, std::vector<size_t> > mTables;
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//worker.h
class Worker {
public:
Worker(size_t workerId) : mWorkerId(workerId) {}
bool UpdateTable(const std::string &tableName, const std::string &content);
bool ReadTable(const std::string &tableName, std::string &content);
void CleanTable(const std::string &tableName);
protected:
virtual bool CreateFile(const std::string &fileId);
virtual bool DeleteFile(const std::string &fileId);
virtual bool WriteToFile(const std::string &fileId, const std::string &content);
virtual bool ReadFile(const std::string &fileId, std::string &content);
size_t mWorkerId;
std::map<std::string, std::vector<std::string> > mTableFiles;
};

Server对象负责将字符串分配给多个Worker对象,Worker对象将字符串拆分为若干小文件存储。
Server中有存储Worker编号到Worker指针的映射。
Server通过调用UpdateTable这个函数来向所有Worker上传名为table内容为content的字符串。
Server会依次调用所有Worker的UpdateTable函数,如果所有Worker都成功则用CleanTable这个函数来清理老文件,
生成一个从table到已经上传了的worker编号的映射,之后返回上传成功。
如果任意一个Worker上传失败,Server需要用CleanTable让所有Worker回滚到上传之前的状态,之后返回更新失败。
Worker中存有table到一组真实文件路径的映射。
Worker中可以通过CreateFile,DeleteFile,ReadFile,WriteFile,函数来创建,删除,读出和写入文件。

现将Server::UpdateTable,Worker::UpdateTable,Worker::cleanTable这3个函数挖空,作为某课程的作业交给同学们实现。
你的任务是编写验证这些程序正确性的测试代码。要测试的流程如下:

(a) 正常的文件上传流程
原有代码中Worker的CreateFile,DeleteFile,ReadFile,WriteFile函数都是必定成功的。
所以这一步需要让Server调用一次UpdateTable,看是否返回上传成功。
(b) 检查Server::mTables的正确性
再添加一个新的TestWorker到TestServer中,之后检查mTableFiles的某些值是否为预期值。
(c) 检查上传的内容的正确性
(d) CreateFile出现问题时,检查回滚逻辑是否实现
新加的TestWorker的CreateFile必定会失败。
此时再向同一个table进行一次UpdateTable,看是否返回了更新失败。
之后检查原先的内容是否仍然可读。
(e) 检查多余的文件是否已经被删除
移除TestWorker,检查Worker::mTableFiles是否和(a)之后的一样。
再向同一个table进行一次成功的UpdateTable。
检查(a)之后的mTableFiles中的元素是否都为不可读。

为了方便测试,我们在Server类和Worker类中安插一个友元类Test。

1
2
3
4
5
6
class Server {
public:
friend class Test;
...
...
};

之后,请你对上述测试流程,完善下面的测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <vector>
#include <iostream>
#include "server.h"
#include "worker.h"
using namespace std;
class TestWorker:public Worker {
public:
TestWorker(size_t workerId): Worker(workerId) {}
private:
bool CreateFile(const string &fileId) {
return false;
}
};
class TestServer:public Server {
public:
TestServer() {
for(size_t i = 0; i < 3; ++i)
mWorkers[i] = new Worker(i);
}
virtual ~TestServer() {
//Delete all Worker instances
for(map<size_t, Worker *>::iterator it = mWorkers.begin(); it != mWorkers.end(); ++it)
delete it->second;
}
};
class Test {
public:
static void test();
private:
static bool test(TestServer* testServer);
};
void Test::test() {
TestServer* demo = new TestServer();
if(test(demo))
cout << "test pass." << endl;
else
cout << "test fail." << endl;
}
bool Test::test(TestServer* testServer) {
string tableName = "demo";
string content = "this is a demo string";
string content2 = "this is another demo string";
//Test the normal updating process
if(!testServer->UpdateTable(tableName, content))
return false;
//Insert an abnormal worker
testServer->mWorkers[3] = new TestWorker(3);
//Get mTables from the server
vector<size_t> & workers = testServer->mTables["demo"];
if(workers.size() != 3)
return false;
map<string, vector<string> > mTableFiles[3];
//Judge each string we just updated to the workers
for(size_t i = 0; i < workers.size(); ++i) {
string result;
testServer->mWorkers[workers[i]]->ReadTable(tableName, result);
if(result != content)
return false;
mTableFiles[i]=testServer->mWorkers[workers[i]]->mTableFiles;
}
//Now the updating process will fail because the abnormal worker
if(testServer->UpdateTable(tableName, content2) != false)
return false;
//The old string should be readable.
for(size_t i = 0; i < workers.size(); ++i) {
string result;
testServer->mWorkers[workers[i]]->ReadTable(tableName, result);
if(result != content)
return false;
}
//Remove the abnormal worker
delete(testServer->mWorkers[3]);
testServer->mWorkers.erase(testServer->mWorkers.find(3));
//mTableFiles should equal to the backup one
for(size_t i = 0; i < workers.size(); ++i)
if(!(mTableFiles[i]==testServer->mWorkers[workers[i]]->mTableFiles))
return false;
testServer->UpdateTable(tableName, content2);
//None of the pieces of the old string is readable now
for(size_t i = 0; i < workers.size(); ++i){
vector<string> & backupFilelist=mTableFiles[i][tableName];
vector<string> & targetFilelist=testServer->mWorkers[workers[i]]->mTableFiles[tableName];
//Check the pieces one by one
targetFilelist.reserve(1);
string result;
for(vector<string>::iterator it=backupFilelist.begin();it!=backupFilelist.end();it++){
targetFilelist[0]=*it;
if(testServer->mWorkers[workers[i]]->ReadTable(tableName,result))
return false;
}
}
return true;
}
int main(){
Test::test();
return 0;
}

点评

这句话其实最早我写在题目正文里面了。。

对程序安全性而言,友元类哪里是friend,简直就是fiend啊!!