001 package org.andromda.translation.ocl.testsuite;
002
003 import java.util.Iterator;
004 import java.util.Map;
005 import junit.framework.TestCase;
006 import junit.framework.TestResult;
007 import junit.framework.TestSuite;
008 import org.andromda.core.AndroMDA;
009 import org.andromda.core.configuration.Configuration;
010 import org.andromda.core.configuration.Model;
011 import org.andromda.core.configuration.Namespaces;
012 import org.andromda.core.configuration.Repository;
013 import org.andromda.core.metafacade.MetafacadeFactory;
014 import org.andromda.core.metafacade.ModelAccessFacade;
015 import org.andromda.core.repository.Repositories;
016 import org.andromda.core.repository.RepositoryFacade;
017 import org.andromda.core.translation.Expression;
018 import org.andromda.core.translation.ExpressionTranslator;
019 import org.andromda.core.translation.TranslationUtils;
020 import org.apache.commons.lang.StringUtils;
021 import org.apache.log4j.Logger;
022
023 /**
024 * This object is used to test Translations during development.
025 *
026 * @author Chad Brandon
027 */
028 public final class TranslationTestProcessor
029 extends TestCase
030 {
031 private static final Logger logger = Logger.getLogger(TranslationTestProcessor.class);
032
033 /**
034 * The shared instance of this class.
035 */
036 private static TranslationTestProcessor instance;
037
038 /**
039 * Gets the shared instance of this class.
040 *
041 * @return the shared instance of this class.
042 */
043 public static final TranslationTestProcessor instance()
044 {
045 if (instance == null)
046 {
047 instance = new TranslationTestProcessor();
048 }
049 return instance;
050 }
051
052 private TranslationTestProcessor()
053 {
054 super();
055 }
056
057 /**
058 * Sets whether or not to use the trace translator.
059 *
060 * @param useTraceTranslator true/false
061 */
062 public void setUseTraceTranslator(final boolean useTraceTranslator)
063 {
064 this.useTraceTranslator = useTraceTranslator;
065 }
066
067 /**
068 * Indicates whether or not the TraceTranslator will run instead
069 * of the specified translator. This is helpful, in allowing us to see which
070 * expressions are being parsed in what order, etc.
071 */
072 private boolean useTraceTranslator;
073
074 /**
075 * Thbe name of the translation to test.
076 */
077 private String translationName;
078
079 /**
080 * Sets the name of the translation to test.
081 *
082 * @param translationName the name of the translation to test.
083 */
084 public void setTranslationName(final String translationName)
085 {
086 this.translationName = translationName;
087 }
088
089 /**
090 * The location of the directory that contains the test source.
091 */
092 private String testSourceDirectory;
093
094 /**
095 * Sets the location of the directory that contains the test souce.
096 *
097 * @param testSourceDirectory
098 */
099 public void setTestSourceDirectory(final String testSourceDirectory)
100 {
101 this.testSourceDirectory = testSourceDirectory;
102 }
103
104 /**
105 * Handles the discovering of the translation tests.
106 */
107 private static final TranslationTestDiscoverer testDiscoverer = TranslationTestDiscoverer.instance();
108
109 /**
110 * The translation that is currently being tested.
111 */
112 private String testTranslation = null;
113
114 /**
115 * Basic constructor - called by the test runners.
116 */
117 private TranslationTestProcessor(String testName)
118 {
119 super(testName);
120 }
121
122 /**
123 * The test result
124 */
125 private TestResult testResult;
126
127 /**
128 * Sets the test result in which the result of the run will be stored.
129 *
130 * @param testResult the test result instance.
131 */
132 public void setResult(final TestResult testResult)
133 {
134 this.testResult = testResult;
135 }
136
137 /**
138 * Runs the test suite.
139 *
140 * @see junit.framework.TestCase#run()
141 */
142 public void runSuite()
143 {
144 if (this.testResult == null)
145 {
146 throw new TranslationTestProcessorException(
147 "You must set the test result before attempting to run the suite");
148 }
149 final AndroMDA andromda = AndroMDA.newInstance();
150 MetafacadeFactory factory = MetafacadeFactory.getInstance();
151 andromda.initialize(this.configuration);
152 factory.setNamespace(Namespaces.DEFAULT);
153 if (this.model == null)
154 {
155 final Repositories repositoriesContainer = Repositories.instance();
156 final Repository[] repositories = this.configuration.getRepositories();
157 if (repositories != null && repositories.length > 0)
158 {
159 final int numberOfRepositories = repositories.length;
160 for (int ctr = 0; ctr < numberOfRepositories; ctr++)
161 {
162 final Repository repository = repositories[ctr];
163 final Model[] models = repository.getModels();
164 if (models != null)
165 {
166 // - we just load only the first model (since it doesn't
167 // make sense
168 // to test with more than one model)
169 final Model model = models[0];
170 repositoriesContainer.loadModel(model);
171 final RepositoryFacade repositoryImplementation =
172 repositoriesContainer.getImplementation(repository.getName());
173 this.model = repositoryImplementation.getModel();
174
175 // - make sure the factory has access to the model
176 factory.setModel(this.model, model.getType());
177 }
178 }
179 }
180 }
181 this.getSuite().run(this.testResult);
182 andromda.shutdown();
183 }
184
185 /**
186 * Assembles and retrieves the test suite of all known transation-library tests.
187 *
188 * @return non-null test suite
189 */
190 private TestSuite getSuite()
191 {
192 testDiscoverer.discoverTests(this.testSourceDirectory);
193 final Map tests = testDiscoverer.getTests();
194 final TestSuite suite = new TestSuite();
195 for (final Iterator iterator = tests.keySet().iterator(); iterator.hasNext();)
196 {
197 final TranslationTestProcessor unitTest = new TranslationTestProcessor("testTranslation");
198
199 // - pass on the variables to each test
200 unitTest.setConfiguration(this.configuration);
201 unitTest.setTestTranslation((String) iterator.next());
202 unitTest.model = this.model;
203 suite.addTest(unitTest);
204 }
205 return suite;
206 }
207
208 private Configuration configuration;
209
210 /**
211 * Sets AndroMDA configuration instance.
212 *
213 * @param configuration the AndroMDA configuration instance.
214 */
215 public void setConfiguration(final Configuration configuration)
216 {
217 this.configuration = configuration;
218 }
219
220 /**
221 * Sets the value for the test translation which is the translation that
222 * will be tested.
223 *
224 * @param testTranslation
225 */
226 private void setTestTranslation(String testTranslation)
227 {
228 this.testTranslation = testTranslation;
229 }
230
231 /**
232 * The model that was loaded.
233 */
234 private ModelAccessFacade model;
235
236 /**
237 * Finds the classifier having <code>fullyQualifiedName</code> in the
238 * model.
239 *
240 * @param translation the translation we're using
241 * @param expression the expression from which we'll find the model element.
242 * @return Object the found model element.
243 */
244 protected Object findModelElement(
245 String translation,
246 String expression)
247 {
248 final String methodName = "TranslationTestProcessor.findClassifier";
249 Object element = null;
250 if (StringUtils.isNotBlank(expression))
251 {
252 if (this.model == null)
253 {
254 throw new RuntimeException(methodName + " could not retrieve model from repository");
255 }
256
257 ContextElementFinder finder = new ContextElementFinder(model);
258 finder.translate(
259 translation,
260 expression,
261 null);
262 element = finder.getContextElement();
263
264 if (element == null)
265 {
266 final String message =
267 "No element found in model in expression --> '" + expression +
268 "' for translation ' " + translation + "', please check your model or your TranslationTest file";
269 logger.error("ERROR! " + message);
270 TestCase.fail(message);
271 }
272 }
273 return element;
274 }
275
276 /**
277 * Tests the current translation set in the currentTestTranslation property.
278 */
279 public void testTranslation()
280 {
281 String translation = this.testTranslation;
282
283 if (this.shouldTest(translation))
284 {
285 if (logger.isInfoEnabled())
286 {
287 logger.info("testing translation --> '" + translation + '\'');
288 }
289
290 TranslationTest test = testDiscoverer.getTest(translation);
291
292 Map<String, ExpressionTest> expressions = test.getExpressionConfigs();
293
294 if (expressions != null)
295 {
296 for (String fromExpression : expressions.keySet())
297 {
298 // if the fromExpression body isn't defined, skip expression
299 // test
300 if (StringUtils.isEmpty(fromExpression))
301 {
302 if (logger.isInfoEnabled())
303 {
304 logger.info(
305 "No body for the 'from' element was defined " + "within translation test --> '" +
306 test.getUri() + "', please define the body of this element with " +
307 "the expression you want to translate from");
308 }
309 continue;
310 }
311
312 Expression translated;
313 if (useTraceTranslator)
314 {
315 translated = TraceTranslator.getInstance().translate(
316 translation,
317 fromExpression,
318 null);
319 }
320 else
321 {
322 final ExpressionTest expressionConfig = expressions.get(fromExpression);
323 String toExpression = expressionConfig.getTo();
324
325 Object modelElement = null;
326
327 // - only find the model element if we have a model
328 // defined in our AndroMDA configuration
329 final Repository[] repositories = this.configuration.getRepositories();
330 if (repositories != null && repositories.length > 0)
331 {
332 modelElement = this.findModelElement(
333 translation,
334 fromExpression);
335 }
336 else
337 {
338 logger.info("No repositories defined in configuration, not finding for model elements");
339 }
340
341 translated =
342 ExpressionTranslator.instance().translate(
343 translation,
344 fromExpression,
345 modelElement);
346
347 if (translated != null)
348 {
349 // remove the extra whitespace from both so as to
350 // have an accurrate comarison
351 toExpression = TranslationUtils.removeExtraWhitespace(toExpression);
352 if (logger.isInfoEnabled())
353 {
354 logger.info("translated: --> '" + translated.getTranslatedExpression() + '\'');
355 logger.info("expected: --> '" + toExpression + '\'');
356 }
357 TestCase.assertEquals(
358 toExpression,
359 translated.getTranslatedExpression());
360 }
361 }
362 }
363 }
364 } else
365 {
366 if (logger.isInfoEnabled())
367 {
368 logger.info("skipping translation --> '" + translation + '\'');
369 }
370 }
371 }
372
373 /**
374 * This method returns true if we should allow the translation to be tested.
375 * This is so we can specify on the command line, the translation to be
376 * tested, if we don't want all to be tested.
377 *
378 * @param translation
379 * @return boolean
380 */
381 private boolean shouldTest(String translation)
382 {
383 translation = StringUtils.trimToEmpty(translation);
384 return StringUtils.isEmpty(this.translationName) ||
385 (StringUtils.isNotBlank(this.translationName) && this.translationName.equals(translation));
386 }
387
388 /**
389 * Shuts down this instance.
390 */
391 public void shutdown()
392 {
393 testDiscoverer.shutdown();
394 TranslationTestProcessor.instance = null;
395 }
396 }