View Javadoc
1   /*
2    * Copyright (c) 2014 Zeligsoft (2009) Limited.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    */
8   package org.eclipse.papyrus.designer.languages.cpp.codegen.tests;
9   
10  import static org.junit.Assert.assertNotNull;
11  import static org.junit.Assert.assertTrue;
12  import static org.junit.Assert.fail;
13  
14  import java.io.InputStream;
15  import java.util.List;
16  import java.util.Scanner;
17  
18  import org.eclipse.core.resources.IFile;
19  import org.eclipse.core.resources.IFolder;
20  import org.eclipse.core.resources.IProject;
21  import org.eclipse.core.resources.ResourcesPlugin;
22  import org.eclipse.core.runtime.CoreException;
23  import org.eclipse.core.runtime.IProgressMonitor;
24  import org.eclipse.core.runtime.NullProgressMonitor;
25  import org.eclipse.emf.common.util.URI;
26  import org.eclipse.emf.ecore.EObject;
27  import org.eclipse.papyrus.editor.PapyrusMultiDiagramEditor;
28  import org.eclipse.papyrus.infra.core.sashwindows.di.service.IPageManager;
29  import org.eclipse.papyrus.infra.core.services.ServicesRegistry;
30  import org.eclipse.papyrus.infra.services.openelement.service.OpenElementService;
31  import org.eclipse.papyrus.infra.ui.editor.IMultiDiagramEditor;
32  import org.eclipse.papyrus.junit.utils.EditorUtils;
33  import org.eclipse.papyrus.junit.utils.rules.HouseKeeper;
34  import org.eclipse.papyrus.uml.tools.model.UmlModel;
35  import org.eclipse.papyrus.uml.tools.model.UmlUtils;
36  import org.eclipse.ui.IWorkbenchPage;
37  import org.eclipse.ui.PlatformUI;
38  import org.eclipse.ui.handlers.IHandlerService;
39  import org.junit.AfterClass;
40  import org.junit.BeforeClass;
41  import org.junit.ClassRule;
42  import org.junit.Test;
43  
44  @SuppressWarnings("nls")
45  public class CppCodegenTest {
46  
47  	static {
48  		// This system property avoids opening dialogs during Papyrus operations.  It must
49  		// be set before trying to load any of the Papyrus classes.
50  		System.setProperty("papyrus.run-headless", Boolean.TRUE.toString());
51  	}
52  
53  	private static final String GENERATE_COMMAND_ID = "org.eclipse.papyrus.designer.languages.cpp.codegen.command";
54  
55  	private static final String EDITOR_ID = "org.eclipse.papyrus.infra.core.papyrusEditor";
56  
57  	private static final String ModelProjectName = "project";
58  
59  	private static final String ModelName = "CppCodegenTest.uml";
60  
61  	private static final String GenProjectName = "org.eclipse.papyrus.cppgen.CppCodegenTest";
62  
63  	private static final String GenFolderName = "CppCodegenTest";
64  
65  	private static final String ModelPath = "/" + ModelProjectName + '/' + ModelName;
66  
67  	private static final String ExpectedGenFolderName = "ExpectedModel";
68  
69  	private static final String ExpectedGenFolderPath = "/" + ModelProjectName + '/' + ExpectedGenFolderName;
70  
71  	private static final String Class1_fragment = "_x6ArECrKEeOncLSXAkfRBA";
72  
73  	private static final String Class2_fragment = "_0E-t0CrKEeOncLSXAkfRBA";
74  
75  	private static final String Class3_fragment = "_29UM4CrKEeOncLSXAkfRBA";
76  
77  	private static final String Class4_fragment = "_-j3HgCrKEeOncLSXAkfRBA";
78  
79  	private static final String Class5_fragment = "_hTMV0CumEeOcwILjsIdkdw";
80  
81  	private static final String Class6_fragment = "_OJ7A0CxUEeOcwILjsIdkdw";
82  
83  	private static final String Class7_fragment = "_ZqD3YCz9EeOcwILjsIdkdw";
84  
85  	private static final String Class8_fragment = "_qS9iYDEmEeOSfbt-FmCdoQ";
86  
87  	private static final String Class9_fragment = "_jcK5MDG0EeOOEc5pE2t6oQ";
88  
89  	private static final String Package1_fragment = "_nZ5DgDEmEeOSfbt-FmCdoQ";
90  
91  	private static final String Model_fragment = "_1_ToYCoNEeOncLSXAkfRBA";
92  
93  	private static final IProgressMonitor npm = new NullProgressMonitor();
94  
95  	private static IProject modelProject;
96  
97  	private static IProject genProject;
98  
99  	private static IHandlerService handlerService;
100 
101 	private static URI modelUri;
102 
103 	private static URI genCodeUri;
104 
105 	private static IWorkbenchPage page;
106 
107 	private static PapyrusMultiDiagramEditor editor;
108 
109 	private static OpenElementService elementActivator;
110 
111 	private static UmlModel model;
112 
113 	@ClassRule
114 	public static HouseKeeper.Static houseKeeper = new HouseKeeper.Static();
115 
116 	@BeforeClass
117 	public static void loadProject() throws Exception {
118 
119 		handlerService = (IHandlerService)PlatformUI.getWorkbench().getService(IHandlerService.class);
120 		
121 		// Create a project to hold the model, make sure it has C++ natures to avoid confirmation
122 		// dialog during code generation.
123 		modelProject = houseKeeper.createProject(ModelProjectName);
124 
125 		// Import the LANGUAGE_NAME .h and .cpp files from this test plugin to the runtime workbench.
126 		IFile diFile = houseKeeper.createFile(modelProject, "CppCodegenTest.di", "resources/CppCodegenTest.di");
127 		houseKeeper.createFile(modelProject, "CppCodegenTest.notation", "resources/CppCodegenTest.notation");
128 		houseKeeper.createFile(modelProject, "CppCodegenTest.uml", "resources/CppCodegenTest.uml");
129 
130 		String[] targetFiles = new String[]{
131 			"Class1.h", "Class1.cpp",
132 			"Class2.h", "Class2.cpp",
133 			"Class3.h", "Class3.cpp",
134 			"Class4.h", "Class4.cpp",
135 			"Class5.h", "Class5.cpp",
136 			"Class6.h", "Class6.cpp",
137 			"Class7.h", "Class7.cpp",
138 			"Pkg_CppCodegenTest.h",
139 			"Package1/Class8.h", "Package1/Class8.cpp",
140 			"Package1/Class9.h", "Package1/Class9.cpp",
141 			"Package1/Pkg_Package1.h"
142 		};
143 
144 		for (String targetFile: targetFiles){
145 			houseKeeper.createFile(modelProject, targetFile, ExpectedGenFolderName + "/" + targetFile);
146 		}
147 
148 		// Setup the base modelUri for convenience in the test cases.
149 		modelUri = URI.createPlatformResourceURI(ModelPath, true);
150 		assertNotNull(modelUri);
151 
152 		// Get the currently active workbench page so we can control the editor that is about
153 		// to be opened.
154 		page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
155 
156 		editor = (PapyrusMultiDiagramEditor) EditorUtils.openEditor(diFile);
157 		assertNotNull(editor);
158 
159 		model = UmlUtils.getUmlModel();
160 		assertNotNull(model);
161 
162 		// Model elements are selected with the appropriate service.
163 		elementActivator = editor.getServicesRegistry().getService(OpenElementService.class);
164 		assertNotNull(elementActivator);
165 
166 		//Setup the base genCodeUri for convenience in the test cases.
167 		genCodeUri = URI.createPlatformPluginURI(ExpectedGenFolderPath, true);
168 		assertNotNull(genCodeUri);
169 	}
170 
171 	@AfterClass
172 	public static void cleanup() throws Exception {
173 		if(modelProject == null) {
174 			return;
175 		}
176 
177 		// Close the editor without saving anything.  This is required in order to avoid dialogs
178 		// about "do you want to save these changes" when the project is deleted.
179 		if(page != null) {
180 			page.closeAllEditors(false);
181 		}
182 
183 		// Now we should be able to delete the project without opening any confirmation dialogs.
184 		modelProject.delete(true, true, npm);
185 		modelProject = null;
186 	}
187 
188 	@Test
189 	public void testGenerateClass1() throws Exception {
190 		assertGenerate(Class1_fragment);
191 		assertGeneratedMatchesExpected("Class1.h");
192 		assertGeneratedMatchesExpected("Class1.cpp");
193 	}
194 
195 	@Test
196 	public void testGenerateClass2() throws Exception {
197 		assertGenerate(Class2_fragment);
198 		assertGeneratedMatchesExpected("Class2.h");
199 		assertGeneratedMatchesExpected("Class2.cpp");
200 	}
201 
202 	@Test
203 	public void testGenerateClass3() throws Exception {
204 		assertGenerate(Class3_fragment);
205 		assertGeneratedMatchesExpected("Class3.h");
206 		assertGeneratedMatchesExpected("Class3.cpp");
207 	}
208 
209 	@Test
210 	public void testGenerateClass4() throws Exception {
211 		assertGenerate(Class4_fragment);
212 		assertGeneratedMatchesExpected("Class4.h");
213 		assertGeneratedMatchesExpected("Class4.cpp");
214 	}
215 
216 	@Test
217 	public void testGenerateClass5() throws Exception {
218 		assertGenerate(Class5_fragment);
219 		assertGeneratedMatchesExpected("Class5.h");
220 		assertGeneratedMatchesExpected("Class5.cpp");
221 	}
222 
223 	@Test
224 	public void testGenerateClass6() throws Exception {
225 		assertGenerate(Class6_fragment);
226 		assertGeneratedMatchesExpected("Class6.h");
227 		assertGeneratedMatchesExpected("Class6.cpp");
228 	}
229 
230 	@Test
231 	public void testGenerateClass7() throws Exception {
232 		assertGenerate(Class7_fragment);
233 		assertGeneratedMatchesExpected("Class7.h");
234 		assertGeneratedMatchesExpected("Class7.cpp");
235 	}
236 
237 	@Test
238 	public void testGenerateClass8() throws Exception {
239 		assertGenerate(Class8_fragment);
240 		assertGeneratedMatchesExpected("Class8.h", "Package1");
241 		assertGeneratedMatchesExpected("Class8.cpp", "Package1");
242 	}
243 
244 	@Test
245 	public void testGenerateClass9() throws Exception {
246 		assertGenerate(Class9_fragment);
247 		assertGeneratedMatchesExpected("Class9.h", "Package1");
248 		assertGeneratedMatchesExpected("Class9.cpp", "Package1");
249 	}
250 
251 	@Test
252 	public void testGeneratePackage1NamespaceHeader() throws Exception {
253 		assertGenerate(Package1_fragment);
254 		assertGeneratedMatchesExpected("Pkg_Package1.h", "Package1");
255 	}
256 
257 
258 	@Test
259 	public void testGenerateModelNamespaceHeader() throws Exception {
260 		assertGenerate(Model_fragment);
261 		assertGeneratedMatchesExpected("Pkg_CppCodegenTest.h");
262 	}
263 
264 	private void assertGenerate(String fragment) throws Exception {
265 		selectSemanticElement(fragment);
266 		handlerService.executeCommand(GENERATE_COMMAND_ID, null);
267 	}
268 
269 	private void selectSemanticElement(String uriFragment) throws Exception {
270 		URI elementUri = modelUri.appendFragment(uriFragment);
271 		EObject semantic = model.getResource().getResourceSet().getEObject(elementUri, true);
272 
273 		// #openSemanticElement returns the editor if successful and null otherwise
274 
275 		// the open often fails if pages are passed in, so we first try to open without specifying
276 		// pages
277 		IMultiDiagramEditor editor = elementActivator.openSemanticElement(semantic);//, pages.toArray());
278 		assertNotNull(editor);
279 
280 		// If there isn't an active editor, then we try passing in the list of pages.  From observation,
281 		// this is needed on the first call (first test case) but causes problems on later ones.
282 		if(editor.getActiveEditor() == null) {
283 			ServicesRegistry registry = (ServicesRegistry)editor.getAdapter(ServicesRegistry.class);
284 			assertNotNull(registry);
285 
286 			IPageManager pageManager = registry.getService(IPageManager.class);
287 			assertNotNull(pageManager);
288 
289 			List<Object> pages = pageManager.allPages();
290 			assertNotNull(pages);
291 			assertTrue(pages.size() > 0);
292 
293 			editor = elementActivator.openSemanticElement(semantic, pages.toArray());
294 			assertNotNull(editor);
295 		}
296 
297 		// make sure there is an active editor so that the selection will be available
298 		assertNotNull(editor.getActiveEditor());
299 	}
300 
301 	private static String getFileContents(IFile file) throws CoreException {
302 		InputStream inStream = file.getContents();
303 		assertNotNull("Contents of file \"" + file.getName() + "\" are empty.", inStream);
304 		String content = null;
305 		Scanner s = new Scanner(inStream);
306 
307 		s.useDelimiter("\\Z");
308 
309 		content = s.hasNext() ? s.next() : "";
310 		s.close();
311 
312 		return content;// == null ? null : content.replaceAll("\\s+", " ").trim();
313 	}
314 
315 	private IProject getGeneratedProject() {
316 		if(genProject != null) {
317 			return genProject;
318 		}
319 
320 		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(GenProjectName);
321 		if(project == null || !project.exists()) {
322 			throw new AssertionError("Generated project not found");
323 		}
324 
325 		return genProject = project;
326 	}
327 
328 
329 	/**
330 	 * Compare the files in folder with what we expect to see. When comparing
331 	 * file content, filter out whitespace which will replace all whitespace
332 	 * with a single space in the actual file content and the expected file
333 	 * content in order to avoid problems with differences caused by code
334 	 * formatting options where the test suite is run. Then a simple string
335 	 * comparison is done.
336 	 */
337 	private void assertGeneratedMatchesExpected(String fileName, String... depthSegments) throws Exception {
338 		IFolder generatedFolder = getGeneratedProject().getFolder(GenFolderName);
339 		assertTrue("Default generated folder \"" + GenFolderName + "\" was not generated", generatedFolder.exists());
340 
341 		/* TEST-GENERATED PACKAGE FOLDER */
342 		IFolder generatedPackageFolder = null;
343 		for(int i = 0; i < depthSegments.length; i++) {
344 			if(i == 0) {
345 				generatedPackageFolder = generatedFolder.getFolder(depthSegments[i]);
346 			} else {
347 				generatedPackageFolder = generatedPackageFolder.getFolder(depthSegments[i]);
348 			}
349 			assertTrue("Package folder \"" + depthSegments[i] + "\" was not generated.", generatedPackageFolder.exists());
350 		}
351 
352 		/* TEST-GENERATED FILE */
353 		IFile generatedFile = null;
354 		if(generatedPackageFolder != null) {
355 			generatedFile = generatedPackageFolder.getFile(fileName);
356 		} else {
357 			generatedFile = generatedFolder.getFile(fileName);
358 		}
359 		assertTrue("File " + fileName + " was not generated.", generatedFile.exists());
360 		String fileContent = getFileContents(generatedFile);
361 
362 
363 		/* PREVIOUSLY GENERATED PACKAGE FOLDER */
364 		IFolder expectedFolder = null;
365 		for(int i = 0; i < depthSegments.length; i++) {
366 			if(i == 0) {
367 				expectedFolder = modelProject.getFolder(depthSegments[i]);
368 			} else {
369 				expectedFolder = expectedFolder.getFolder(depthSegments[i]);
370 			}
371 			assertTrue("Package folder \"" + depthSegments[i] + "\" was not generated.", expectedFolder.exists());
372 		}
373 
374 		/* PREVIOUSLY GENERATED FILE */
375 		IFile expectedFile = null;
376 		if(expectedFolder != null) {
377 			expectedFile = expectedFolder.getFile(fileName);
378 		} else {
379 			expectedFile = modelProject.getFile(fileName);
380 		}
381 		assertTrue("File " + fileName + " was not generated.", expectedFile.exists());
382 		String expectedFileContent = getFileContents(expectedFile);
383 
384 		assertContentMatches(fileName, fileContent, expectedFileContent);
385 	}
386 
387 	private static void assertContentMatches(String filename, String generated, String expected) {
388 		Scanner expectedScanner = new Scanner(expected);
389 		char[] strippedGen = generated.replaceAll("\\s+", "").trim().toCharArray();
390 		int genCharsTraversed = 0;
391 		boolean outofchars = false;
392 
393 		/*
394 		 * line by line in expected
395 		 * char by char in generated
396 		 * compare char by char expected to generated until no more chars in line
397 		 * if not matching then print line expected against line generated by
398 		 * keeping track of the amount of chars traversed, then traverse the
399 		 * generated with white characters
400 		 */
401 		try {
402 			int lineNumber = 1;
403 			int lineCharBegin = 0;
404 			for(; !outofchars && expectedScanner.hasNextLine(); ++lineNumber) {
405 				lineCharBegin = genCharsTraversed;
406 				String eLine = expectedScanner.nextLine();
407 				String strippedELine = eLine.replaceAll("\\s+", "").trim();
408 				char[] strippedExpected = strippedELine.toCharArray();
409 				for(int i = 0; i < strippedExpected.length; i++) {
410 					if(strippedExpected[i] != strippedGen[genCharsTraversed]) {
411 						fail(filename + ':' + lineNumber + "expected '" + eLine.trim() + "'but found '" + rebuildStringForLineError(generated.trim(), eLine.trim(), i, genCharsTraversed, lineCharBegin) + "'");
412 					}
413 					genCharsTraversed++;
414 					if(genCharsTraversed == strippedGen.length) {
415 						outofchars = true;
416 					}
417 				}
418 			}
419 			if(expectedScanner.hasNextLine()) {
420 				fail(filename + ':' + lineNumber + " expected '" + expectedScanner.nextLine() + "' but found end-of-file");
421 			} else if(!outofchars) {
422 				fail(filename + ':' + lineNumber + " expected end-of-file but found '" + rebuildStringForEndOfFileError(generated.trim(), genCharsTraversed) + '\'');
423 			}
424 		} finally {
425 			if(expectedScanner != null) {
426 				expectedScanner.close();
427 			}
428 		}
429 	}
430 
431 	private static String rebuildStringForLineError(String generatedString, String expectedLine, int beginInExpectedLine, int genCharsTraversed, int firstCharInExpLine) {
432 		String brokenLine = "";
433 
434 		int lengthFromFirstDiff = expectedLine.replaceAll("\\s+", "").length() - beginInExpectedLine;
435 		char[] generatedChars = generatedString.replaceAll("\\s+", " ").toCharArray();
436 		int nonwhitechars = 0;
437 		for(int i = 0; i < generatedChars.length; i++) {
438 			if(generatedChars[i] != ' ') {
439 				nonwhitechars++;
440 			}
441 			if(nonwhitechars >= firstCharInExpLine && nonwhitechars < genCharsTraversed + lengthFromFirstDiff) {
442 				//start copying
443 				brokenLine += generatedChars[i];
444 			} else if(nonwhitechars == genCharsTraversed + lengthFromFirstDiff) {
445 				//copy number of characters for the length of the expected line
446 				if(generatedChars[i + 1] != '\0') {
447 					brokenLine += "...";
448 				}
449 				break;
450 			}
451 		}
452 
453 		return brokenLine;
454 	}
455 
456 	private static String rebuildStringForEndOfFileError(String generatedString, int genCharsTraversed) {
457 		String brokenLine = "";
458 
459 		char[] generatedChars = generatedString.replaceAll("\\s+", " ").toCharArray();
460 		int nonwhitechars = 0;
461 		for(int i = 0; i < generatedChars.length; i++) {
462 			if(generatedChars[i] != ' ') {
463 				nonwhitechars++;
464 			}
465 			if(nonwhitechars > genCharsTraversed && generatedChars[i] != '\0') {
466 				//start copying
467 				brokenLine += generatedChars[i];
468 			} else if(nonwhitechars == genCharsTraversed + 15 || generatedChars[i] == '\0') {
469 				//copy only 15 chars
470 				if(generatedChars[i + 1] != '\0') {
471 					brokenLine += "...";
472 				}
473 				break;
474 			}
475 		}
476 		return brokenLine;
477 	}
478 }