26 | | A report is received in XML format from the build slave, for example: |
| 26 | As depicated in the following diagram, report data is stored in two tables in the database: `bitten_report` and `bitten_report_item`. |
| 27 | |
| 28 | [[Image(tables.png)]] |
| 29 | |
| 30 | There is one `bitten_report` record per report. This record associates the report with a step of a specific build (see ObjectModel), and stores the ''category'' and the ''generator'' of the report. The category can be seen as indicator of the schema with which the report data complies. For example, there's a category "test" for test results, and a category "coverage" for code coverage. Test results data may be generated by different recipe commands and tools, but as long as they comply with the schema of the "test" category, the data can be analyzed and visualized by the same generic components. |
| 31 | |
| 32 | The report data itself is stored in the `bitten_report_item` table. The basic assumption is that a report consists of a flat, unordered list of data points, each having a variable set of named properties. The names and values of these properties are storeds in the `name` and `value` columns of the `bitten_report_item` table, respectively. The `item` column simply contains a number that groups the individual properties into "report items". |
| 33 | |
| 34 | == Mapping XML Reports to Database Tables == |
| 35 | |
| 36 | A report is received in XML format from the build slave. For example, a report generated by the `<python:unittest>` [wiki:RecipeCommands recipe command] might look like this: |
| 50 | For such a report, Bitten first inserts a record into the `bitten_report` table, taking the category and generator from the corresponding attributes of the `<report>` element. Next, a couple of records are inserted into the `bitten_report_item` table for every immediate child element of `<report>`; one for every attribute of the child element. In addition, the tag name is stored as a property named `type`. |
| 51 | |
| 52 | Children of the child element are also mapped to properties: here the tag name of the "grandchild" is used as the property name, and its´ body text is used as the property value. |
| 53 | |
| 54 | So the following snippets would be stored in exactly the same way: |
| 55 | |
| 56 | {{{ |
| 57 | #!xml |
| 58 | <test duration="0.073" status="success" fixture="bitten.tests.model.BuildConfigTestCase" |
| 59 | name="test_config_update_name" file="bitten/tests/model.py" |
| 60 | stdout="Renaming build configuration"> |
| 61 | </test> |
| 62 | }}} |
| 63 | {{{ |
| 64 | #!xml |
| 65 | <test> |
| 66 | <duration>0.073</duration> |
| 67 | <status>success</status> |
| 68 | <fixture>bitten.tests.model.BuildConfigTestCase</fixture> |
| 69 | <name>test_config_update_name</name> |
| 70 | <file>bitten/tests/model.py</file> |
| 71 | <stdout>Renaming build configuration</stdout> |
| 72 | </test> |
| 73 | }}} |
| 74 | |
| 75 | These two methods can be mixed. When multiple child elements with the same tag name are encountered, only the last one is stored. |
| 76 | |
| 77 | The tables will contain the following data for this report: |
| 78 | |
| 79 | '''`bitten_report`''': |
| 80 | || id || build || step || category || generator || |
| 81 | || 123 || 456 || `foo` || `test` || `http://bitten.cmlenz.net/tools/python#unittest` || |
| 82 | |
| 83 | '''`bitten_report_item`''': |
| 84 | || report || item || name || value || |
| 85 | || 123 || 1 || `type` || `test` || |
| 86 | || 123 || 1 || `duration` || `0.073` || |
| 87 | || 123 || 1 || `status` || `success` || |
| 88 | || 123 || 1 || `fixture` || `bitten.tests.model.BuildConfigTestCase` || |
| 89 | || 123 || 1 || `name` || `test_config_update_name` || |
| 90 | || 123 || 1 || `file` || `bitten/tests/model.py` || |
| 91 | || 123 || 1 || `stdout` || `Renaming build configuration` || |
| 92 | |
38 | | TBW |
| 95 | Querying the report data is not straight-forward due to the generic nature of the tables. This usually requires a number of self-joins. |
| 96 | |
| 97 | For example, the following query aggregates the number of failed/succeeded unit tests by fixture: |
| 98 | |
| 99 | {{{ |
| 100 | #!sql |
| 101 | SELECT item_fixture.value AS fixture, item_file.value AS file, |
| 102 | COUNT(item_success.value) AS num_success, |
| 103 | COUNT(item_failure.value) AS num_failure, |
| 104 | COUNT(item_error.value) AS num_error |
| 105 | FROM bitten_report AS report |
| 106 | LEFT OUTER JOIN bitten_report_item AS item_fixture |
| 107 | ON (item_fixture.report=report.id AND item_fixture.name='fixture') |
| 108 | LEFT OUTER JOIN bitten_report_item AS item_file |
| 109 | ON (item_file.report=report.id AND item_file.item=item_fixture.item AND |
| 110 | item_file.name='file') |
| 111 | LEFT OUTER JOIN bitten_report_item AS item_success |
| 112 | ON (item_success.report=report.id AND item_success.item=item_fixture.item AND |
| 113 | item_success.name='status' AND item_success.value='success') |
| 114 | LEFT OUTER JOIN bitten_report_item AS item_failure |
| 115 | ON (item_failure.report=report.id AND item_failure.item=item_fixture.item AND |
| 116 | item_failure.name='status' AND item_failure.value='failure') |
| 117 | LEFT OUTER JOIN bitten_report_item AS item_error |
| 118 | ON (item_error.report=report.id AND item_error.item=item_fixture.item AND |
| 119 | item_error.name='status' AND item_error.value='error') |
| 120 | WHERE category='test' AND build=%s |
| 121 | GROUP BY file, fixture ORDER BY fixture |
| 122 | }}} |
| 123 | |
| 124 | ''(This code is from the [source://trunk/bitten/trac_ext/summarizers.py TestResultsSummarizer].)'' |