[ROSAUTOTEST]
[reactos.git] / rostests / rosautotest / CJournaledTestList.cpp
1 /*
2 * PROJECT: ReactOS Automatic Testing Utility
3 * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation
4 * PURPOSE: Class implementing a journaled test list for the Crash Recovery feature
5 * COPYRIGHT: Copyright 2009 Colin Finck <colin@reactos.org>
6 */
7
8 #include "precomp.h"
9
10 static const char szJournalHeader[] = "RAT_J-V1";
11 static const WCHAR szJournalFileName[] = L"rosautotest.journal";
12
13 /**
14 * Constructs a CJournaledTestList object for an associated CTest-derived object.
15 *
16 * @param Test
17 * Pointer to a CTest-derived object, for which this test list shall serve.
18 */
19 CJournaledTestList::CJournaledTestList(CTest* Test)
20 : CTestList(Test)
21 {
22 WCHAR JournalFile[MAX_PATH];
23
24 m_hJournal = INVALID_HANDLE_VALUE;
25
26 /* Build the path to the journal file */
27 if(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, JournalFile) != S_OK)
28 FATAL("SHGetFolderPathW failed\n");
29
30 m_JournalFile = JournalFile;
31 m_JournalFile += L"\\rosautotest\\";
32
33 /* Create the directory if necessary */
34 if(GetFileAttributesW(m_JournalFile.c_str()) == INVALID_FILE_ATTRIBUTES)
35 CreateDirectoryW(m_JournalFile.c_str(), NULL);
36
37 m_JournalFile += szJournalFileName;
38
39 /* Check if the journal already exists */
40 if(GetFileAttributesW(m_JournalFile.c_str()) == INVALID_FILE_ATTRIBUTES)
41 WriteInitialJournalFile();
42 else
43 LoadJournalFile();
44 }
45
46 /**
47 * Destructs a CJournaledTestList object.
48 */
49 CJournaledTestList::~CJournaledTestList()
50 {
51 if(m_hJournal != INVALID_HANDLE_VALUE)
52 CloseHandle(m_hJournal);
53 }
54
55 /**
56 * Opens the journal file through the CreateFileW API using the m_hJournal handle.
57 *
58 * @param DesiredAccess
59 * dwDesiredAccess parameter passed to CreateFileW
60 *
61 * @param CreateNew
62 * true if the journal file shall be created, false if an existing one shall be opened
63 */
64 void
65 CJournaledTestList::OpenJournal(DWORD DesiredAccess, bool CreateNew)
66 {
67 m_hJournal = CreateFileW(
68 m_JournalFile.c_str(),
69 DesiredAccess,
70 0,
71 NULL,
72 (CreateNew ? CREATE_ALWAYS : OPEN_EXISTING),
73 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH,
74 NULL
75 );
76
77 if(m_hJournal == INVALID_HANDLE_VALUE)
78 FATAL("CreateFileW failed\n");
79 }
80
81 /**
82 * Serializes a std::string and writes it into the opened journal file.
83 *
84 * @param String
85 * The std::string to serialize
86 *
87 * @see UnserializeFromBuffer
88 */
89 void
90 CJournaledTestList::SerializeIntoJournal(const string& String)
91 {
92 DWORD BytesWritten;
93 WriteFile(m_hJournal, String.c_str(), String.size() + 1, &BytesWritten, NULL);
94 FlushFileBuffers(m_hJournal);
95 }
96
97 /**
98 * Serializes a std::wstring and writes it into the opened journal file.
99 *
100 * @param String
101 * The std::wstring to serialize
102 *
103 * @see UnserializeFromBuffer
104 */
105 void
106 CJournaledTestList::SerializeIntoJournal(const wstring& String)
107 {
108 DWORD BytesWritten;
109 WriteFile(m_hJournal, String.c_str(), (String.size() + 1) * sizeof(WCHAR), &BytesWritten, NULL);
110 FlushFileBuffers(m_hJournal);
111 }
112
113 /**
114 * Unserializes the next std::string from the journal buffer.
115 * The passed buffer pointer will point at the next element afterwards.
116 *
117 * @param Buffer
118 * Pointer to a pointer to a char array containing the journal buffer
119 *
120 * @param Output
121 * The std::string to unserialize the value into.
122 */
123 void
124 CJournaledTestList::UnserializeFromBuffer(char** Buffer, string& Output)
125 {
126 Output = string(*Buffer);
127 *Buffer += Output.size() + 1;
128 }
129
130 /**
131 * Unserializes the next std::wstring from the journal buffer.
132 * The passed buffer pointer will point at the next element afterwards.
133 *
134 * @param Buffer
135 * Pointer to a pointer to a char array containing the journal buffer
136 *
137 * @param Output
138 * The std::wstring to unserialize the value into.
139 */
140 void
141 CJournaledTestList::UnserializeFromBuffer(char** Buffer, wstring& Output)
142 {
143 Output = wstring((PWSTR)*Buffer);
144 *Buffer += (Output.size() + 1) * sizeof(WCHAR);
145 }
146
147 /**
148 * Gets all tests to be run and writes an initial journal file with this information.
149 */
150 void
151 CJournaledTestList::WriteInitialJournalFile()
152 {
153 char TerminatingNull = 0;
154 CTestInfo* TestInfo;
155 DWORD BytesWritten;
156
157 StringOut("Writing initial journal file...\n\n");
158
159 m_ListIterator = 0;
160
161 /* Store all command lines in the m_List vector */
162 while((TestInfo = m_Test->GetNextTestInfo()) != 0)
163 {
164 m_List.push_back(*TestInfo);
165 delete TestInfo;
166 }
167
168 /* Serialize the vector and the iterator into a file */
169 OpenJournal(GENERIC_WRITE, true);
170
171 WriteFile(m_hJournal, szJournalHeader, sizeof(szJournalHeader), &BytesWritten, NULL);
172 WriteFile(m_hJournal, &m_ListIterator, sizeof(m_ListIterator), &BytesWritten, NULL);
173
174 for(size_t i = 0; i < m_List.size(); i++)
175 {
176 SerializeIntoJournal(m_List[i].CommandLine);
177 SerializeIntoJournal(m_List[i].Module);
178 SerializeIntoJournal(m_List[i].Test);
179 }
180
181 WriteFile(m_hJournal, &TerminatingNull, sizeof(TerminatingNull), &BytesWritten, NULL);
182 FlushFileBuffers(m_hJournal);
183
184 CloseHandle(m_hJournal);
185 m_hJournal = INVALID_HANDLE_VALUE;
186
187 /* m_ListIterator will be incremented before its first use */
188 m_ListIterator = (size_t)-1;
189 }
190
191 /**
192 * Loads the existing journal file and sets all members to the values saved in that file.
193 */
194 void
195 CJournaledTestList::LoadJournalFile()
196 {
197 char* Buffer;
198 char* pBuffer;
199 char FileHeader[sizeof(szJournalHeader)];
200 DWORD BytesRead;
201 DWORD RemainingSize;
202
203 StringOut("Loading journal file...\n\n");
204
205 OpenJournal(GENERIC_READ);
206 RemainingSize = GetFileSize(m_hJournal, NULL);
207
208 /* Verify the header of the journal file */
209 ReadFile(m_hJournal, FileHeader, sizeof(szJournalHeader), &BytesRead, NULL);
210 RemainingSize -= BytesRead;
211
212 if(BytesRead != sizeof(szJournalHeader))
213 EXCEPTION("Journal file contains no header!\n");
214
215 if(strcmp(FileHeader, szJournalHeader))
216 EXCEPTION("Journal file has an unsupported header!\n");
217
218 /* Read the iterator */
219 ReadFile(m_hJournal, &m_ListIterator, sizeof(m_ListIterator), &BytesRead, NULL);
220 RemainingSize -= BytesRead;
221
222 if(BytesRead != sizeof(m_ListIterator))
223 EXCEPTION("Journal file contains no m_ListIterator member!\n");
224
225 /* Read the rest of the file into a buffer */
226 Buffer = new char[RemainingSize];
227 pBuffer = Buffer;
228 ReadFile(m_hJournal, Buffer, RemainingSize, &BytesRead, NULL);
229
230 CloseHandle(m_hJournal);
231 m_hJournal = NULL;
232
233 /* Now recreate the m_List vector out of that information */
234 while(*pBuffer)
235 {
236 CTestInfo TestInfo;
237
238 UnserializeFromBuffer(&pBuffer, TestInfo.CommandLine);
239 UnserializeFromBuffer(&pBuffer, TestInfo.Module);
240 UnserializeFromBuffer(&pBuffer, TestInfo.Test);
241
242 m_List.push_back(TestInfo);
243 }
244
245 delete[] Buffer;
246 }
247
248 /**
249 * Writes the current m_ListIterator value into the journal.
250 */
251 void
252 CJournaledTestList::UpdateJournal()
253 {
254 DWORD BytesWritten;
255
256 OpenJournal(GENERIC_WRITE);
257
258 /* Skip the header */
259 SetFilePointer(m_hJournal, sizeof(szJournalHeader), NULL, FILE_CURRENT);
260
261 WriteFile(m_hJournal, &m_ListIterator, sizeof(m_ListIterator), &BytesWritten, NULL);
262 FlushFileBuffers(m_hJournal);
263
264 CloseHandle(m_hJournal);
265 m_hJournal = NULL;
266 }
267
268 /**
269 * Interface to other classes for receiving information about the next test to be run.
270 *
271 * @return
272 * A pointer to a CTestInfo object, which contains all available information about the next test.
273 * The caller needs to free that object.
274 */
275 CTestInfo*
276 CJournaledTestList::GetNextTestInfo()
277 {
278 CTestInfo* TestInfo;
279
280 /* Always jump to the next test here.
281 - If we're at the beginning of a new test list, the iterator will be set to 0.
282 - If we started with a loaded one, we assume that the test m_ListIterator is currently set
283 to crashed, so we move to the next test. */
284 ++m_ListIterator;
285
286 /* Check whether the iterator would already exceed the number of stored elements */
287 if(m_ListIterator == m_List.size())
288 {
289 /* Delete the journal and return no pointer */
290 DeleteFileW(m_JournalFile.c_str());
291
292 TestInfo = NULL;
293 }
294 else
295 {
296 /* Update the journal with the current iterator and return the test information */
297 UpdateJournal();
298
299 TestInfo = new CTestInfo(m_List[m_ListIterator]);
300 }
301
302 return TestInfo;
303 }