/testability-explorer/src/test/java/com/google/test/metric/MetricComputerTest.java

http://testability-explorer.googlecode.com/ · Java · 591 lines · 436 code · 102 blank · 53 comment · 1 complexity · 79a6ab29bcd912049e1e80f794dfc9a7 MD5 · raw file

  1. /*
  2. * Copyright 2007 Google Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.google.test.metric;
  17. import com.google.test.metric.report.DrillDownReportGenerator;
  18. import com.google.test.metric.testing.MetricComputerBuilder;
  19. import com.google.test.metric.testing.MetricComputerJavaDecorator;
  20. import java.io.ByteArrayOutputStream;
  21. import java.io.PrintStream;
  22. import java.util.List;
  23. import javax.swing.JLabel;
  24. public class MetricComputerTest extends AutoFieldClearTestCase {
  25. private MetricComputerJavaDecorator computer;
  26. private final ClassRepository repo = new JavaClassRepository();
  27. @Override
  28. protected void setUp() throws Exception {
  29. super.setUp();
  30. RegExpWhiteList regExpWhitelist = new RegExpWhiteList("java.");
  31. regExpWhitelist.addPackage("javax.");
  32. MetricComputer toDecorate = new MetricComputerBuilder().withWhitelist(regExpWhitelist)
  33. .withClassRepository(repo).build();
  34. computer = new MetricComputerJavaDecorator(toDecorate, repo);
  35. }
  36. public static class Medium {
  37. public Medium() {
  38. statiCost1();
  39. cost2();
  40. }
  41. /**
  42. * I cost 1
  43. */
  44. public static int statiCost1() {
  45. int i = 0;
  46. return i > 0 ? 1 : 2;
  47. }
  48. /**
  49. * I cost 2, but I am instance method so I can be overridden. so my cost may
  50. * be avoided in most cases.
  51. */
  52. public int cost2() {
  53. int i = 0;
  54. return i > 0 ? i > 1 ? 1 : 2 : 2;
  55. }
  56. /**
  57. * I cost 2, but I am a <em>final</em> instance method that can not be overridden.
  58. * My cost is unavoidable.
  59. */
  60. public final int finalCost2() {
  61. int i = 0;
  62. return i > 0 ? i > 1 ? 1 : 2 : 2;
  63. }
  64. /**
  65. * I am instance method hence you will have to add the cost of constructor
  66. * to me. (by myself I cost 4)
  67. */
  68. public Object testMethod4() {
  69. int i = 0;
  70. i = i > 0 ? 1 : 2;
  71. i = i > 0 ? 1 : 2;
  72. i = i > 0 ? 1 : 2;
  73. i = i > 0 ? 1 : 2;
  74. return new Object();
  75. }
  76. }
  77. public void testMediumCost1() throws Exception {
  78. MethodInfo method = repo.getClass(Medium.class.getCanonicalName()).getMethod("int statiCost1()");
  79. assertFalse(method.canOverride());
  80. MethodCost cost = computer.compute(Medium.class, "int statiCost1()");
  81. assertEquals(1l, cost.getTotalCost().getCyclomaticComplexityCost());
  82. }
  83. /**
  84. * Since cost2 is called twice, once by our test and once by constructor we
  85. * don't want to add it twice. The constructor adds 1. The direct method call
  86. * adds 2. So the total cost is 3.
  87. */
  88. public void testMediumCost2() throws Exception {
  89. MethodInfo method = repo.getClass(Medium.class.getCanonicalName()).getMethod("int cost2()");
  90. assertTrue(method.canOverride());
  91. MethodCost cost = computer.compute(Medium.class, "int cost2()");
  92. assertEquals(3l, cost.getTotalCost().getCyclomaticComplexityCost());
  93. }
  94. public void testMediumFinalCost2() throws Exception {
  95. MethodInfo method = repo.getClass(Medium.class.getCanonicalName()).getMethod("int finalCost2()");
  96. assertFalse(method.canOverride());
  97. }
  98. /**
  99. * Cost of the constructor needs to add only the cost of the static method it calls.
  100. * (The static method can not be overridden). The cost of the instance method can be
  101. * overridden in a subclass.
  102. */
  103. public void testMediumInit() throws Exception {
  104. MethodInfo method = repo.getClass(Medium.class.getCanonicalName()).getMethod("Medium()");
  105. assertFalse(method.canOverride());
  106. MethodCost cost = computer.compute(Medium.class, "Medium()");
  107. assertEquals(1l, cost.getTotalCost().getCyclomaticComplexityCost());
  108. }
  109. /**
  110. * Method4 is cost of 4 by itself, but one has to add the cost of constructor
  111. * since it is an instance method. The constructor is 0 but calls two methods:
  112. * cost1 method is static and can not be intercepted hence it has to be added.
  113. * cost2 method is instance and can be overridden hence we don't add that
  114. * cost.
  115. */
  116. public void testMediumMethod4() throws Exception {
  117. MethodCost cost = computer.compute(Medium.class,
  118. "java.lang.Object testMethod4()");
  119. assertEquals(5l, cost.getTotalCost().getCyclomaticComplexityCost());
  120. }
  121. public static class Node {
  122. public String cost1() {
  123. int a = 0;
  124. return a == 2 ? "" : null;
  125. }
  126. }
  127. public static class TreeInjection {
  128. private Node subTitle; // non-injectable
  129. public Node title = new Node(); // injectable b/c public field (only after constructor)
  130. private final Node footnote; // injectable via constructor
  131. public TreeInjection(Node footnote) {
  132. this.footnote = footnote;
  133. }
  134. public String titleTcc0() {
  135. return title.cost1();
  136. }
  137. public String subTitleTcc1() {
  138. return subTitle.cost1();
  139. }
  140. public String footnoteTcc0() {
  141. return footnote.cost1();
  142. }
  143. public String veryExpensive() {
  144. return "".toLowerCase();
  145. }
  146. }
  147. public void testTreeConstructorHasZeroCost() throws Exception {
  148. MethodCost cost = computer.compute(TreeInjection.class,
  149. "TreeInjection(com.google.test.metric.MetricComputerTest.Node)");
  150. assertEquals(0l, cost.getTotalCost().getCyclomaticComplexityCost());
  151. assertEquals(0l, cost.getTotalCost().getGlobalCost());
  152. }
  153. public void testTreeTitleTcc0CostIsZeroBecauseInjectable() throws Exception {
  154. MethodCost cost = computer.compute(TreeInjection.class,
  155. "java.lang.String titleTcc0()");
  156. assertEquals(0l, cost.getTotalCost().getCyclomaticComplexityCost());
  157. }
  158. public void testTreeSubTitleTcc1CostIsOneBecauseNonInjectable() throws Exception {
  159. MethodCost cost = computer.compute(TreeInjection.class,
  160. "java.lang.String subTitleTcc1()");
  161. assertEquals(1l, cost.getTotalCost().getCyclomaticComplexityCost());
  162. }
  163. public void testTreeFootnoteTcc0CostIsZeroBecauseInjectable() throws Exception {
  164. MethodCost cost = computer.compute(TreeInjection.class,
  165. "java.lang.String footnoteTcc0()");
  166. assertEquals(0l, cost.getTotalCost().getCyclomaticComplexityCost());
  167. }
  168. static class ChoseConstructor {
  169. ChoseConstructor() {
  170. }
  171. ChoseConstructor(Object a) {
  172. }
  173. ChoseConstructor(Object a, int c, int d) {
  174. }
  175. ChoseConstructor(Object a, Object b) {
  176. }
  177. }
  178. public void testChooseConstructorWithMostNonPrimitiveParameters() throws Exception {
  179. ClassInfo classInfo = repo.getClass(ChoseConstructor.class.getCanonicalName());
  180. MethodInfo constructor = classInfo.getConstructorWithMostNonPrimitiveParameters();
  181. assertEquals("ChoseConstructor(java.lang.Object, java.lang.Object)", constructor.getName());
  182. }
  183. static class Singleton {
  184. private Singleton() {
  185. CostUtil.staticCost1();
  186. }
  187. public void doWork() {
  188. CostUtil.staticCost2();
  189. }
  190. }
  191. public void testIgnoreConstructorsIfAllConstructorsArePrivate() throws Exception {
  192. assertEquals(2L, computer.compute(Singleton.class, "void doWork()").getTotalCost().getCyclomaticComplexityCost());
  193. ClassInfo classInfo = repo.getClass(Singleton.class.getCanonicalName());
  194. MethodInfo constructor = classInfo
  195. .getConstructorWithMostNonPrimitiveParameters();
  196. assertNull("Constructor should not be found when private", constructor);
  197. }
  198. static class StaticInit {
  199. static {
  200. CostUtil.staticCost1();
  201. }
  202. public void doWork() {
  203. CostUtil.staticCost2();
  204. }
  205. }
  206. public void testAddStaticInitializationCost() throws Exception {
  207. assertEquals(3L, computer.compute(StaticInit.class, "void doWork()").getTotalCost().getCyclomaticComplexityCost());
  208. }
  209. static class Setters {
  210. private Object o;
  211. public void setO(Object o) {
  212. this.o = o;
  213. }
  214. public void doWork() {
  215. o.toString();
  216. }
  217. }
  218. public void testSetterInjection() throws Exception {
  219. MethodCost cost = computer.compute(Setters.class, "void doWork()");
  220. assertEquals(0L, cost.getTotalCost().getCyclomaticComplexityCost());
  221. }
  222. static class WholeClassCost {
  223. void methodA() {
  224. CostUtil.staticCost1();
  225. }
  226. void methodB() {
  227. CostUtil.staticCost1();
  228. }
  229. }
  230. public void testComputeClassCost() throws Exception {
  231. ClassCost cost = computer.compute(WholeClassCost.class);
  232. assertEquals(1L, cost.getMethodCost("void methodA()").getTotalCost().getCyclomaticComplexityCost());
  233. assertEquals(1L, cost.getMethodCost("void methodB()").getTotalCost().getCyclomaticComplexityCost());
  234. }
  235. static class Array {
  236. String[] strings;
  237. public void method() {
  238. strings.clone();
  239. }
  240. }
  241. public void testArray() throws Exception {
  242. repo.getClass(String[].class.getName());
  243. computer.compute(repo.getClass(Array.class.getCanonicalName()).getMethod("void method()"));
  244. }
  245. static class InjectableClass {
  246. public void cost4() {
  247. CostUtil.staticCost4();
  248. }
  249. public static void callCost0(InjectableClass ref) {
  250. indirection(ref);
  251. }
  252. private static void indirection(InjectableClass ref) {
  253. ref.cost4();
  254. }
  255. public static void callCost4() {
  256. InjectableClass x = new InjectableClass();
  257. indirection(x);
  258. }
  259. }
  260. public void testInjectabilityIsTransitive() throws Exception {
  261. MethodCost callCost0 = computer.compute(InjectableClass.class, "void callCost0("
  262. + "com.google.test.metric.MetricComputerTest.InjectableClass)");
  263. assertEquals(0L, callCost0.getTotalCost().getCyclomaticComplexityCost());
  264. MethodCost callCost4 = computer.compute(InjectableClass.class, "void callCost4()");
  265. assertEquals(4L, callCost4.getTotalCost().getCyclomaticComplexityCost());
  266. }
  267. static class GlobalState {
  268. int i;
  269. static final String X = "X";
  270. public int inc() {
  271. return i++;
  272. }
  273. public void noop() {
  274. }
  275. @Override
  276. public String toString() {
  277. return X;
  278. }
  279. }
  280. static final GlobalState ref = new GlobalState();
  281. static int count;
  282. static class GlobalStateUser {
  283. // StateLoad: 0
  284. public void noLoad() {
  285. }
  286. // StateLoad: 1
  287. public void accessCount() {
  288. count++;
  289. }
  290. // StateLoad: 0
  291. public void accessFinalState() {
  292. ref.noop();
  293. }
  294. // StateLoad: 0
  295. public void accessFinalState2() {
  296. ref.toString();
  297. }
  298. // StateLoad: 1
  299. public void accessMutableState() {
  300. ref.inc();
  301. }
  302. }
  303. public void testGlobalLoadWhichAccessesFinalShouldBeZero() {
  304. ClassCost cost = computer.compute(GlobalState.class);
  305. MethodCost method = cost.getMethodCost("java.lang.String toString()");
  306. assertEquals(0L, method.getTotalCost().getGlobalCost());
  307. }
  308. public void testGlobalLoadMethodDispatchNoStateAccessShouldBeZero() {
  309. ClassCost cost = computer.compute(GlobalStateUser.class);
  310. assertEquals(0L, cost.getMethodCost("void noLoad()").getTotalCost().getGlobalCost());
  311. assertEquals(0L, cost.getMethodCost("void accessFinalState()").getTotalCost().getGlobalCost());
  312. assertEquals(0L, cost.getMethodCost("void accessFinalState2()").getTotalCost().getGlobalCost());
  313. }
  314. public void testGlobalLoadAccessStateShouldBeOne() {
  315. MethodCost cost = computer.compute(GlobalStateUser.class, "void accessCount()");
  316. assertEquals(1L, cost.getTotalCost().getGlobalCost());
  317. }
  318. public void testGlobalLoadAccessStateThroughFinalShouldBeOne() {
  319. MethodCost cost =
  320. computer.compute(GlobalStateUser.class, "void accessMutableState()");
  321. new DrillDownReportGenerator(new PrintStream(new ByteArrayOutputStream()), new CostModel(),
  322. null, Integer.MAX_VALUE, 0).print("", cost, 10);
  323. assertEquals("Expecting one for read and one for write", 2L,
  324. cost.getTotalCost().getGlobalCost());
  325. }
  326. public void testJavaLangObjectParsesCorrectly() throws Exception {
  327. repo.getClass(Object.class.getCanonicalName());
  328. }
  329. public static class CostPerLine {
  330. static void main(){
  331. CostUtil.staticCost0(); // line0
  332. CostUtil.staticCost1(); // line1
  333. CostUtil.staticCost2(); // line2
  334. }
  335. }
  336. public void testCostPerLine() throws Exception {
  337. MethodCost cost = computer.compute(CostPerLine.class, "void main()");
  338. assertEquals(3, cost.getTotalCost().getCyclomaticComplexityCost());
  339. List<? extends ViolationCost> lineNumberCosts = cost.getViolationCosts();
  340. assertEquals(3, lineNumberCosts.size());
  341. MethodInvocationCost line0 = (MethodInvocationCost) lineNumberCosts.get(0);
  342. MethodInvocationCost line1 = (MethodInvocationCost) lineNumberCosts.get(1);
  343. MethodInvocationCost line2 = (MethodInvocationCost) lineNumberCosts.get(2);
  344. int methodStartingLine = cost.getMethodLineNumber();
  345. assertEquals(0, line0.getMethodCost().getTotalCost().getCyclomaticComplexityCost());
  346. assertEquals(methodStartingLine + 0, line0.getLocation().getLineNumber());
  347. assertEquals(1, line1.getMethodCost().getTotalCost().getCyclomaticComplexityCost());
  348. assertEquals(methodStartingLine + 1, line1.getLocation().getLineNumber());
  349. assertEquals(2, line2.getMethodCost().getTotalCost().getCyclomaticComplexityCost());
  350. assertEquals(methodStartingLine + 2, line2.getLocation().getLineNumber());
  351. }
  352. public static class WhiteListTest {
  353. public void testMethod() {
  354. new String(new byte[0]);
  355. }
  356. }
  357. public void testWhiteList() throws Exception {
  358. RegExpWhiteList customWhitelist = new RegExpWhiteList("java.lang");
  359. MetricComputer toDecorate = new MetricComputerBuilder().withClassRepository(repo)
  360. .withWhitelist(customWhitelist).build();
  361. computer = new MetricComputerJavaDecorator(toDecorate, repo);
  362. MethodCost cost = computer.compute(WhiteListTest.class, "void testMethod()");
  363. assertEquals(0L, cost.getTotalCost().getGlobalCost());
  364. }
  365. static class DoubleCountClassConst {
  366. static int x;
  367. static {
  368. x = 1;
  369. }
  370. }
  371. public void testDoubleCountClassConst() throws Exception {
  372. ClassCost cost = computer.compute(DoubleCountClassConst.class);
  373. assertEquals(1, cost.getMethodCost("DoubleCountClassConst()").getTotalCost().getGlobalCost());
  374. }
  375. static enum TestEnum1{ ONE }
  376. public void testEnumerationIsZero() throws Exception {
  377. RegExpWhiteList customWhitelist = new RegExpWhiteList("java.");
  378. MetricComputer toDecorate = new MetricComputerBuilder().withClassRepository(repo)
  379. .withWhitelist(customWhitelist).build();
  380. computer = new MetricComputerJavaDecorator(toDecorate, repo);
  381. ClassCost cost = computer.compute(TestEnum1.class);
  382. assertEquals(0, cost.getMethodCost("<static init>()").getTotalCost().getGlobalCost());
  383. }
  384. public void testSyntheticEnumValuesAccessorClassIsZero() throws Exception {
  385. RegExpWhiteList customWhitelist = new RegExpWhiteList("java.");
  386. MetricComputer toDecorate = new MetricComputerBuilder().withClassRepository(repo)
  387. .withWhitelist(customWhitelist).build();
  388. computer = new MetricComputerJavaDecorator(toDecorate, repo);
  389. ClassCost cost;
  390. try {
  391. cost = computer.compute("com.google.test.metric.InnerEnumHolder.1");
  392. } catch (ClassNotFoundException e) {
  393. // Eclipse names its enumerations differently
  394. cost = computer.compute("com.google.test.metric.InnerEnumHolder.NodeType");
  395. }
  396. assertEquals(0, cost.getMethodCost("<static init>()").getTotalCost().getGlobalCost());
  397. }
  398. private final String testValue = null;
  399. class InnerClassTest {
  400. public void test() {
  401. testValue.indexOf(0);
  402. }
  403. }
  404. public void XtestInnerClassInjectability() throws Exception {
  405. MethodCost cost = computer.compute(InnerClassTest.class, "void test()");
  406. assertEquals(0, cost.getTotalCost().getCyclomaticComplexityCost());
  407. }
  408. private static class ScoreTooHigh {
  409. public void doMockery(Builder builder) {
  410. Product product = builder.build();
  411. product.execute();
  412. }
  413. public static class Product {
  414. public void execute() {
  415. CostUtil.staticCost2();
  416. }
  417. }
  418. public static class Builder {
  419. public Product build() {
  420. CostUtil.staticCost4();
  421. return null;
  422. }
  423. }
  424. }
  425. public void testReturnValueFromInjectableIsInjectable() throws Exception {
  426. MethodCost cost = computer.compute(ScoreTooHigh.class,
  427. "void doMockery(com.google.test.metric.MetricComputerTest.ScoreTooHigh.Builder)");
  428. assertEquals(0, cost.getTotalCost().getCyclomaticComplexityCost());
  429. }
  430. public static class NonDeterministicCostUtil {
  431. public static int global;
  432. public int notGlobal;
  433. public int value;
  434. private int trinary(boolean bool) {
  435. try {
  436. return !bool ? notGlobal : global;
  437. } catch (Exception e) {
  438. return 1;
  439. }
  440. }
  441. public void test() {
  442. value = trinary(false);
  443. }
  444. }
  445. public void testWhenLeftAndRightSideOfTrinaryReturnDifferentCostUseHigherOne()
  446. throws Exception {
  447. MethodCost cost = computer.compute(NonDeterministicCostUtil.class,
  448. "void test()");
  449. assertEquals(1, cost.getTotalCost().getGlobalCost());
  450. }
  451. public static class HasIrrelevantImplicitCost {
  452. public HasIrrelevantImplicitCost() {
  453. CostUtil.staticCost2();
  454. }
  455. public void setNoCost(int i) {
  456. }
  457. public void setWithCost(int i) {
  458. CostUtil.staticCost1();
  459. }
  460. public void execute() {
  461. }
  462. }
  463. public void testZeroImplicitCostNotCounted() throws Exception {
  464. MethodCost cost = computer.compute(HasIrrelevantImplicitCost.class, "void execute()");
  465. List<? extends ViolationCost> implicitViolationCosts = cost.getImplicitViolationCosts();
  466. assertEquals(2, implicitViolationCosts.size());
  467. MethodInvocationCost constructor = (MethodInvocationCost) implicitViolationCosts.get(0);
  468. assertEquals("implicit cost from construction", constructor.getReason());
  469. assertEquals(2, constructor.getCost().getCyclomaticComplexityCost());
  470. assertTrue(constructor.getMethodCost().getImplicitViolationCosts().isEmpty());
  471. MethodInvocationCost setter = (MethodInvocationCost) implicitViolationCosts.get(1);
  472. assertEquals("implicit cost calling all setters", setter.getReason());
  473. assertEquals("void setWithCost(int)", setter.getMethodCost().getMethodName());
  474. }
  475. public void testConstructorDoesntCountSetters() throws Exception {
  476. MethodCost cost = computer.compute(HasIrrelevantImplicitCost.class,
  477. "HasIrrelevantImplicitCost()");
  478. assertEquals(0, cost.getImplicitViolationCosts().size());
  479. }
  480. static class MyLabel extends JLabel {
  481. void doThing() {
  482. }
  483. }
  484. public void testJavaXSuperClassSettersArentCountedAgainstMe() throws Exception {
  485. ClassCost cost = computer.compute(MyLabel.class);
  486. assertEquals(0, cost.getTotalComplexityCost());
  487. assertEquals(0, cost.getTotalGlobalCost());
  488. }
  489. }