]> source.dussan.org Git - sonarqube.git/commitdiff
Merge remote-tracking branch 'origin/branch-4.4'
authorStas Vilchik <vilchiks@gmail.com>
Wed, 23 Jul 2014 12:03:42 +0000 (14:03 +0200)
committerStas Vilchik <vilchiks@gmail.com>
Wed, 23 Jul 2014 12:03:42 +0000 (14:03 +0200)
Conflicts:
sonar-server/src/main/coffee/component-viewer/header/scm-header.coffee
sonar-server/src/main/coffee/component-viewer/main.coffee

1  2 
server/sonar-server/src/main/java/org/sonar/server/test/ws/TestsShowAction.java
server/sonar-server/src/test/java/org/sonar/server/test/ws/TestsShowActionTest.java
server/sonar-web/src/main/coffee/component-viewer/mixins/main-scm.coffee
server/sonar-web/src/main/hbs/component-viewer/header/cw-scm-header.hbs
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index ca756649a807b36eaecccb9722a0ffc5c64d43a0,0000000000000000000000000000000000000000..4213c5553beed43c63a67f606f4eab0ceedf2a7f
mode 100644,000000..100644
--- /dev/null
@@@ -1,160 -1,0 +1,161 @@@
-         json.prop("durationInMs", Long.parseLong(cursor.getAttrValue("time")));
 +/*
 + * SonarQube, open source software quality management tool.
 + * Copyright (C) 2008-2014 SonarSource
 + * mailto:contact AT sonarsource DOT com
 + *
 + * SonarQube is free software; you can redistribute it and/or
 + * modify it under the terms of the GNU Lesser General Public
 + * License as published by the Free Software Foundation; either
 + * version 3 of the License, or (at your option) any later version.
 + *
 + * SonarQube is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * Lesser General Public License for more details.
 + *
 + * You should have received a copy of the GNU Lesser General Public License
 + * along with this program; if not, write to the Free Software Foundation,
 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 + */
 +
 +package org.sonar.server.test.ws;
 +
 +import com.google.common.io.Resources;
 +import org.codehaus.staxmate.SMInputFactory;
 +import org.codehaus.staxmate.in.SMHierarchicCursor;
 +import org.codehaus.staxmate.in.SMInputCursor;
 +import org.sonar.api.measures.CoreMetrics;
 +import org.sonar.api.server.ws.Request;
 +import org.sonar.api.server.ws.RequestHandler;
 +import org.sonar.api.server.ws.Response;
 +import org.sonar.api.server.ws.WebService;
 +import org.sonar.api.test.MutableTestPlan;
 +import org.sonar.api.test.TestCase;
 +import org.sonar.api.utils.text.JsonWriter;
 +import org.sonar.api.web.UserRole;
 +import org.sonar.core.component.SnapshotPerspectives;
 +import org.sonar.core.measure.db.MeasureDto;
 +import org.sonar.core.persistence.DbSession;
 +import org.sonar.core.persistence.MyBatis;
 +import org.sonar.server.db.DbClient;
 +import org.sonar.server.user.UserSession;
 +
 +import javax.annotation.CheckForNull;
 +import javax.xml.stream.XMLInputFactory;
 +import javax.xml.stream.XMLStreamException;
 +
 +import java.io.StringReader;
 +
 +public class TestsShowAction implements RequestHandler {
 +
 +  private static final String KEY = "key";
 +
 +  private final DbClient dbClient;
 +  private final SnapshotPerspectives snapshotPerspectives;
 +
 +  public TestsShowAction(DbClient dbClient, SnapshotPerspectives snapshotPerspectives) {
 +    this.dbClient = dbClient;
 +    this.snapshotPerspectives = snapshotPerspectives;
 +  }
 +
 +  void define(WebService.NewController controller) {
 +    WebService.NewAction action = controller.createAction("show")
 +      .setDescription("Get the list of test cases of a test plan. Require Browse permission on file's project")
 +      .setSince("4.4")
 +      .setResponseExample(Resources.getResource(getClass(), "tests-example-show.json"))
 +      .setHandler(this);
 +
 +    action
 +      .createParam(KEY)
 +      .setRequired(true)
 +      .setDescription("Test plan key")
 +      .setExampleValue("my_project:/src/test/BarTest.java");
 +  }
 +
 +  @Override
 +  public void handle(Request request, Response response) {
 +    String fileKey = request.mandatoryParam(KEY);
 +    UserSession.get().checkComponentPermission(UserRole.CODEVIEWER, fileKey);
 +
 +    String testData = findTestData(fileKey);
 +    JsonWriter json = response.newJsonWriter().beginObject();
 +    if (testData != null) {
 +      writeFromTestData(testData, json);
 +    } else {
 +      MutableTestPlan testPlan = snapshotPerspectives.as(MutableTestPlan.class, fileKey);
 +      if (testPlan != null) {
 +        writeFromTestable(testPlan, json);
 +      }
 +    }
 +    json.endObject().close();
 +  }
 +
 +  private void writeFromTestable(MutableTestPlan testPlan, JsonWriter json) {
 +    json.name("tests").beginArray();
 +    for (TestCase testCase : testPlan.testCases()) {
 +      json.beginObject();
 +      json.prop("name", testCase.name());
 +      json.prop("status", testCase.status().name());
 +      json.prop("durationInMs", testCase.durationInMs());
 +      json.prop("coveredLines", testCase.countCoveredLines());
 +      json.prop("message", testCase.message());
 +      json.prop("stackTrace", testCase.stackTrace());
 +      json.endObject();
 +    }
 +    json.endArray();
 +  }
 +
 +  private void writeFromTestData(String data, JsonWriter json) {
 +    SMInputFactory inputFactory = initStax();
 +    try {
 +      SMHierarchicCursor root = inputFactory.rootElementCursor(new StringReader(data));
 +      root.advance(); // tests-details
 +      SMInputCursor cursor = root.childElementCursor();
 +      json.name("tests").beginArray();
 +      while (cursor.getNext() != null) {
 +        json.beginObject();
 +
 +        json.prop("name", cursor.getAttrValue("name"));
 +        json.prop("status", cursor.getAttrValue("status").toUpperCase());
++        // time can contain float value, we have to truncate it
++        json.prop("durationInMs", ((Double) Double.parseDouble(cursor.getAttrValue("time"))).longValue());
 +
 +        SMInputCursor errorCursor = cursor.childElementCursor();
 +        if (errorCursor.getNext() != null) {
 +          json.prop("message", errorCursor.getAttrValue("message"));
 +          json.prop("stackTrace", errorCursor.getElemStringValue());
 +        }
 +
 +        json.endObject();
 +      }
 +      json.endArray();
 +    } catch (XMLStreamException e) {
 +      throw new IllegalStateException("XML is not valid: " + e.getMessage(), e);
 +    }
 +  }
 +
 +  @CheckForNull
 +  private String findTestData(String fileKey) {
 +    DbSession session = dbClient.openSession(false);
 +    try {
 +      MeasureDto testData = dbClient.measureDao().findByComponentKeyAndMetricKey(fileKey, CoreMetrics.TEST_DATA_KEY, session);
 +      if (testData != null) {
 +        return testData.getData();
 +      }
 +    } finally {
 +      MyBatis.closeQuietly(session);
 +    }
 +    return null;
 +  }
 +
 +  private SMInputFactory initStax() {
 +    XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
 +    xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
 +    xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
 +    // just so it won't try to load DTD in if there's DOCTYPE
 +    xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
 +    xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
 +    return new SMInputFactory(xmlFactory);
 +  }
 +
 +}
index 49c28868be609a091920dfa2be71acae3180422d,0000000000000000000000000000000000000000..9f7648e975a66afc61fe6588dac40305785d6f18
mode 100644,000000..100644
--- /dev/null
@@@ -1,131 -1,0 +1,145 @@@
 +/*
 + * SonarQube, open source software quality management tool.
 + * Copyright (C) 2008-2014 SonarSource
 + * mailto:contact AT sonarsource DOT com
 + *
 + * SonarQube is free software; you can redistribute it and/or
 + * modify it under the terms of the GNU Lesser General Public
 + * License as published by the Free Software Foundation; either
 + * version 3 of the License, or (at your option) any later version.
 + *
 + * SonarQube is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * Lesser General Public License for more details.
 + *
 + * You should have received a copy of the GNU Lesser General Public License
 + * along with this program; if not, write to the Free Software Foundation,
 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 + */
 +
 +package org.sonar.server.test.ws;
 +
 +import org.junit.Before;
 +import org.junit.Test;
 +import org.junit.runner.RunWith;
 +import org.mockito.Mock;
 +import org.mockito.runners.MockitoJUnitRunner;
 +import org.sonar.api.test.MutableTestCase;
 +import org.sonar.api.test.MutableTestPlan;
 +import org.sonar.api.test.TestCase;
 +import org.sonar.api.web.UserRole;
 +import org.sonar.core.component.SnapshotPerspectives;
 +import org.sonar.core.measure.db.MeasureDto;
 +import org.sonar.core.measure.db.MeasureKey;
 +import org.sonar.core.persistence.DbSession;
 +import org.sonar.server.db.DbClient;
 +import org.sonar.server.measure.persistence.MeasureDao;
 +import org.sonar.server.user.MockUserSession;
 +import org.sonar.server.ws.WsTester;
 +
 +import javax.annotation.Nullable;
 +
 +import static com.google.common.collect.Lists.newArrayList;
 +import static org.mockito.Mockito.mock;
 +import static org.mockito.Mockito.when;
 +
 +@RunWith(MockitoJUnitRunner.class)
 +public class TestsShowActionTest {
 +
 +  static final String TEST_PLAN_KEY = "src/test/java/org/foo/BarTest.java";
 +
 +  @Mock
 +  DbSession session;
 +
 +  @Mock
 +  MeasureDao measureDao;
 +
 +  @Mock
 +  MutableTestPlan testPlan;
 +
 +  @Mock
 +  SnapshotPerspectives snapshotPerspectives;
 +
 +  WsTester tester;
 +
 +  @Before
 +  public void setUp() throws Exception {
 +    DbClient dbClient = mock(DbClient.class);
 +    when(dbClient.openSession(false)).thenReturn(session);
 +    when(dbClient.measureDao()).thenReturn(measureDao);
 +
 +    tester = new WsTester(new TestsWs(new TestsShowAction(dbClient, snapshotPerspectives), mock(TestsTestCasesAction.class), mock(TestsCoveredFilesAction.class)));
 +  }
 +
 +  @Test
 +  public void show() throws Exception {
 +    MockUserSession.set().addComponentPermission(UserRole.CODEVIEWER, "SonarQube", TEST_PLAN_KEY);
 +
 +    when(snapshotPerspectives.as(MutableTestPlan.class, TEST_PLAN_KEY)).thenReturn(testPlan);
 +
 +    MutableTestCase testCase1 = testCase("test1", TestCase.Status.OK, 10L, 32, null, null);
 +    MutableTestCase testCase2 = testCase("test2", TestCase.Status.ERROR, 97L, 21, "expected:<true> but was:<false>",
 +      "java.lang.AssertionError: expected:<true> but was:<false>\n\t" +
 +        "at org.junit.Assert.fail(Assert.java:91)\n\t" +
 +        "at org.junit.Assert.failNotEquals(Assert.java:645)\n\t" +
 +        "at org.junit.Assert.assertEquals(Assert.java:126)\n\t" +
 +        "at org.junit.Assert.assertEquals(Assert.java:145)\n");
 +    when(testPlan.testCases()).thenReturn(newArrayList(testCase1, testCase2));
 +
 +    WsTester.TestRequest request = tester.newGetRequest("api/tests", "show").setParam("key", TEST_PLAN_KEY);
 +
 +    request.execute().assertJson(getClass(), "show.json");
 +  }
 +
 +  @Test
 +  public void show_from_test_data() throws Exception {
 +    MockUserSession.set().addComponentPermission(UserRole.CODEVIEWER, "SonarQube", TEST_PLAN_KEY);
 +
 +    when(measureDao.findByComponentKeyAndMetricKey(TEST_PLAN_KEY, "test_data", session)).thenReturn(MeasureDto.createFor(MeasureKey.of(TEST_PLAN_KEY, "test_data"))
 +      .setTextValue("<tests-details>" +
 +        "<testcase status=\"ok\" time=\"10\" name=\"test1\"/>" +
 +        "<testcase status=\"error\" time=\"97\" name=\"test2\">" +
 +        "<error message=\"expected:&lt;true&gt; but was:&lt;false&gt;\">" +
 +        "<![CDATA[" +
 +        "java.lang.AssertionError: expected:<true> but was:<false>\n\t" +
 +        "at org.junit.Assert.fail(Assert.java:91)\n\t" +
 +        "at org.junit.Assert.failNotEquals(Assert.java:645)\n\t" +
 +        "at org.junit.Assert.assertEquals(Assert.java:126)\n\t" +
 +        "at org.junit.Assert.assertEquals(Assert.java:145)\n" +
 +        "]]>" +
 +        "</error>" +
 +        "</testcase>" +
 +        "</tests-details>"));
 +
 +    WsTester.TestRequest request = tester.newGetRequest("api/tests", "show").setParam("key", TEST_PLAN_KEY);
 +
 +    request.execute().assertJson(getClass(), "show_from_test_data.json");
 +  }
 +
++  @Test
++  public void show_from_test_data_with_a_time_in_float() throws Exception {
++    MockUserSession.set().addComponentPermission(UserRole.CODEVIEWER, "SonarQube", TEST_PLAN_KEY);
++
++    when(measureDao.findByComponentKeyAndMetricKey(TEST_PLAN_KEY, "test_data", session)).thenReturn(MeasureDto.createFor(MeasureKey.of(TEST_PLAN_KEY, "test_data"))
++      .setTextValue("<tests-details>" +
++        "<testcase status=\"ok\" time=\"12.5\" name=\"test1\"/>" +
++        "</tests-details>"));
++
++    WsTester.TestRequest request = tester.newGetRequest("api/tests", "show").setParam("key", TEST_PLAN_KEY);
++
++    request.execute().assertJson(getClass(), "show_from_test_data_with_a_time_in_float.json");
++  }
++
 +  private MutableTestCase testCase(String name, TestCase.Status status, Long durationInMs, int coveredLines, @Nullable String message, @Nullable String stackTrace) {
 +    MutableTestCase testCase = mock(MutableTestCase.class);
 +    when(testCase.name()).thenReturn(name);
 +    when(testCase.status()).thenReturn(status);
 +    when(testCase.durationInMs()).thenReturn(durationInMs);
 +    when(testCase.countCoveredLines()).thenReturn(coveredLines);
 +    when(testCase.message()).thenReturn(message);
 +    when(testCase.stackTrace()).thenReturn(stackTrace);
 +    return testCase;
 +  }
 +
 +}
index 23c899f185fda465fac3aaade1326a23c01e9fce,0000000000000000000000000000000000000000..d13be6b4e186e787df90942365acecaf63958da8
mode 100644,000000..100644
--- /dev/null
@@@ -1,79 -1,0 +1,83 @@@
-       @sourceView.render()
 +define [], () ->
 +
 +  $ = jQuery
 +  API_SCM = "#{baseUrl}/api/sources/scm"
 +
 +
 +  class SCMMixin
 +
 +    requestSCM: (key) ->
 +      $.get API_SCM, key: key, (data) =>
 +        if data?.scm?
 +          @state.set 'hasSCM', true
 +          @source.set scm: data.scm
 +          @augmentWithSCM data.scm
 +
 +
 +    augmentWithSCM: (scm) ->
 +      formattedSource = @source.get 'formattedSource'
 +      scmLength = scm.length
 +      if scmLength > 0
 +        scmIndex = 0
 +        scmCurrent = scm[scmIndex]
 +        scmDetails = {}
 +        formattedSource.forEach (line) ->
 +          if line.lineNumber == scmCurrent[0]
 +            scmDetails = author: scmCurrent[1], date: scmCurrent[2]
 +            if scmIndex < scmLength - 1
 +              scmIndex++
 +              scmCurrent = scm[scmIndex]
 +          line.scm = scmDetails
 +        @source.set 'formattedSource', formattedSource
 +
 +
 +
 +    showSCM: (store = false) ->
 +      @settings.set 'scm', true
 +      @storeSettings() if store
 +      unless @state.get 'hasSCM'
 +        @requestSCM(@key).done => @sourceView.render()
 +      else
 +        @sourceView.render()
 +
 +
 +    hideSCM: (store = false) ->
 +      @settings.set 'scm', false
 +      @storeSettings() if store
 +      @sourceView.render()
 +
 +
++    filterByModifiedLines: ->
++      @filterBySCM()
++
++
 +    filterBySCM: ->
 +      requests = [@requestSCM(@key)]
 +      if @settings.get('issues') && !@state.get('hasIssues')
 +        requests.push @requestIssues @key
 +      $.when.apply($, requests).done =>
 +        @_filterBySCM()
 +
 +
 +    _filterBySCM: () ->
 +      formattedSource = @source.get 'formattedSource'
 +      period = @state.get 'period'
 +      unless period?
 +        return @showAllLines()
 +      else
 +        periodDate = period.get 'sinceDate'
 +      @settings.set 'scm', true
 +      @sourceView.resetShowBlocks()
 +      scmBlockLine = 1
 +      predicate = false
 +      formattedSource.forEach (line) =>
 +        scmBlockDate = new Date line.scm.date
 +        if scmBlockDate >= periodDate
 +          scmBlockLine = line.lineNumber if predicate == false
 +          predicate = true
 +        else if predicate == true
 +          predicate = false
 +          @sourceView.addShowBlock scmBlockLine, line.lineNumber - 1
 +      if predicate
 +        @sourceView.addShowBlock scmBlockLine, _.size @source.get 'source'
++      @sourceView.render()
index d5802abdd570043d7a0752f5a616bd0bd040bde9,0000000000000000000000000000000000000000..599f9f71e33c732612da18a6d2e2053ecf8fc794
mode 100644,000000..100644
--- /dev/null
@@@ -1,5 -1,0 +1,14 @@@
- </div>
 +<div class="component-viewer-header-time-changes">
 +  <a class="highlighted-link js-scm-time-changes">
 +    {{#if period}}Δ {{period.label}}{{else}}<i class="icon-period"></i> {{t 'component_viewer.time_changes'}}{{/if}}
 +  </a>
++</div>
++
++<div class="component-viewer-header-expanded-bar-section">
++  <ul class="component-viewer-header-expanded-bar-section-list">
++    <li><a class="item js-filter-modified-lines">
++      <span>{{t 'component_viewer.scm.modified_lines'}}</span>
++      <i class="icon-chevron-right"></i>
++    </a></li>
++  </ul>
++</div>