Skip to content

Commit

Permalink
Merge pull request #12 from sackfield/fix-remaining-issues
Browse files Browse the repository at this point in the history
Fix Remaining FOSS issues
  • Loading branch information
8W9aG committed Jan 4, 2016
2 parents 81e190f + e3a0c59 commit b6c9108
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 36 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.pyc
/*.egg-info
build/
dist/
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
Contributions are welcomed. Open a pull-request or an issue.

This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code.

[code-of-conduct]: https://github.com/spotify/code-of-conduct/blob/master/code-of-conduct.md
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# :star: The Testing Game :star:
# :star: The Testing Game 1.0 :star:

Welcome to the Testing Game! A simple script that counts the number of Objective-C, Java, C++ or python unit tests in the current working directory within a git repository, and showcases a ranking based on the percentage each developer has written.

Expand All @@ -24,12 +24,19 @@ This script was made to “gameify” testing at Spotify and to continue encoura
The script uses the current working directory to find files it could possibly read (such as `.m`, `.mm` and `.java` files) and performs a `git blame` on these files in order to match tests written to the developers that wrote them.
The owner of the method name of the test is considered the developer that wrote it.

## Dependencies

* [python 2.7.10](https://www.python.org/downloads/release/python-2710/) (for running the script)
* [git 2.6.1](https://git-scm.com/) (for finding the blame information for a given file)

The script should run on any operating system containing these two dependencies.

## Usage

1. Run the Python script from your repository:

```shell
> python testing-game.py
> python testinggame.py
```

2. Mention that you write most unit units of your project on every meeting (no, don’t do that).
Expand Down
16 changes: 16 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from setuptools import setup

setup(name='testinggame',
version='1.0.0',
description='A script for counting the number of tests per developer',
url='http://github.com/spotify/testing-game',
author='Will Sackfield',
author_email='[email protected]',
license='Apache',
packages=['testinggame'],
zip_safe=False,
entry_points={
'console_scripts': [
'testinggame = testinggame:_main'
]
})
160 changes: 126 additions & 34 deletions testing-game.py → testinggame/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# !/usr/bin/python
# -*- coding: utf-8 -*-
'''
* Copyright (c) 2015 Spotify AB.
*
Expand All @@ -18,16 +20,43 @@
* specific language governing permissions and limitations
* under the License.
'''

# !/usr/bin/python
# -*- coding: utf-8 -*-

import argparse
import os
import subprocess


def find_xctest_tests(blame_lines, names, source, xctestsuperclasses):
def _find_name_from_blame(blame_line):
"""
Finds the name of the committer of code given a blame line from git
Args:
blame_line: A string from the git output of the blame for a file.
Returns:
The username as a string of the user to blame
"""
blame_info = blame_line[blame_line.find('(')+1:]
blame_info = blame_info[:blame_info.find(')')]
blame_components = blame_info.split()
name_components = blame_components[:len(blame_components)-4]
return ' '.join(name_components)


def _find_xctest_tests(blame_lines, names, source, xctestsuperclasses):
"""
Finds the number of XCTest cases per user.
Args:
blame_lines: An array where each index is a string containing the git
blame line.
names: The current dictionary containing the usernames as a key and the
number of tests as a value.
source: A string containing the raw source code for the file.
xctestsuperclasses: An array containing alternative superclasses for
the xctest framework.
Returns:
A dictionary built off the names argument containing the usernames as a
key and the number of tests as a value.
"""
xctest_identifiers = ['XCTestCase']
xctest_identifiers.extend(xctestsuperclasses)
contains_test_case = False
Expand All @@ -38,26 +67,35 @@ def find_xctest_tests(blame_lines, names, source, xctestsuperclasses):
if contains_test_case:
for blame_line in blame_lines:
if blame_line.replace(' ', '').find('-(void)test') != -1:
blame_info = blame_line[blame_line.find('(')+1:]
blame_info = blame_info[:blame_info.find(')')]
blame_components = blame_info.split()
name_components = blame_components[:len(blame_components)-4]
name = ' '.join(name_components)
name = _find_name_from_blame(blame_line)
name_count = names.get(name, 0)
names[name] = name_count + 1
return names


def find_java_tests(blame_lines, names, source):
def _find_java_tests(blame_lines, names, source):
"""
Finds the number of Java test cases per user. This will find tests both
with the @Test annotation and the standard test methods.
Args:
blame_lines: An array where each index is a string containing the git
blame line.
names: The current dictionary containing the usernames as a key and the
number of tests as a value.
source: A string containing the raw source code for the file.
Returns:
A dictionary built off the names argument containing the usernames as a
key and the number of tests as a value.
"""
next_is_test = False
for blame_line in blame_lines:
separator = blame_line.find(')')
blame_code_nospaces = blame_line[separator+1:]
blame_code_nospaces = blame_code_nospaces.replace(' ', '')
blame_code_nospaces = blame_code_nospaces.replace('\t', '')
if next_is_test or blame_code_nospaces.startswith('publicvoidtest'):
blame_info = blame_line[:separator]
name = blame_info[blame_info.find('<')+1:blame_info.find('@')]
name = _find_name_from_blame(blame_line)
name_count = names.get(name, 0)
names[name] = name_count + 1
next_is_test = False
Expand All @@ -66,7 +104,20 @@ def find_java_tests(blame_lines, names, source):
return names


def find_boost_tests(blame_lines, names, source):
def _find_boost_tests(blame_lines, names, source):
"""
Finds the number of Boost test cases per user.
Args:
blame_lines: An array where each index is a string containing the git
blame line.
names: The current dictionary containing the usernames as a key and the
number of tests as a value.
source: A string containing the raw source code for the file.
Returns:
A dictionary built off the names argument containing the usernames as a
key and the number of tests as a value.
"""
test_cases = ['BOOST_AUTO_TEST_CASE', 'BOOST_FIXTURE_TEST_CASE']
for blame_line in blame_lines:
contains_test_case = False
Expand All @@ -75,31 +126,55 @@ def find_boost_tests(blame_lines, names, source):
if contains_test_case:
break
if contains_test_case:
blame_info = blame_line[blame_line.find('(')+1:]
blame_info = blame_info[:blame_info.find(')')]
blame_components = blame_info.split()
name_components = blame_components[:len(blame_components)-4]
name = ' '.join(name_components)
name = _find_name_from_blame(blame_line)
name_count = names.get(name, 0)
names[name] = name_count + 1
return names


def find_nose_tests(blame_lines, names, source):
def _find_nose_tests(blame_lines, names, source):
"""
Finds the number of python test cases per user.
Args:
blame_lines: An array where each index is a string containing the git
blame line.
names: The current dictionary containing the usernames as a key and the
number of tests as a value.
source: A string containing the raw source code for the file.
Returns:
A dictionary built off the names argument containing the usernames as a
key and the number of tests as a value.
"""
for blame_line in blame_lines:
separator = blame_line.find(')')
blame_code_nospaces = blame_line[separator+1:]
blame_code_nospaces = blame_code_nospaces.replace(' ', '')
blame_code_nospaces = blame_code_nospaces.replace('\t', '')
if blame_code_nospaces.startswith('deftest_'):
blame_info = blame_line[:separator]
name = blame_info[blame_info.find('<')+1:blame_info.find('@')]
if blame_code_nospaces.startswith('deftest'):
name = _find_name_from_blame(blame_line)
name_count = names.get(name, 0)
names[name] = name_count + 1
return names


def find_git_status(directory, xctestsuperclasses):
def _find_git_status(directory, xctestsuperclasses):
"""
Finds the number of tests per user within a given directory. Note that this
will only work on the root git subdirectory, submodules will not be
counted.
Args:
directory: The path to the directory to scan.
xctestsuperclasses: An array of strings containing names for xctest
superclasses.
Returns:
A dictionary built off the names argument containing the usernames as a
key and the number of tests as a value.
>>> _find_git_status('tests', 'SPTTestCase')
{'Will Sackfield': 6}
"""
names = {}
objc_extensions = ['.m', '.mm']
java_extensions = ['.java']
Expand All @@ -123,23 +198,28 @@ def find_git_status(directory, xctestsuperclasses):
out, err = p.communicate()
blame_lines = out.splitlines()
if fileextension in objc_extensions:
names = find_xctest_tests(blame_lines,
names,
source,
xctestsuperclasses)
names = _find_xctest_tests(blame_lines,
names,
source,
xctestsuperclasses)
if fileextension in java_extensions:
names = find_java_tests(blame_lines, names, source)
if fileextension in cpp_extensions:
names = find_boost_tests(blame_lines,
names = _find_java_tests(blame_lines,
names,
source)
if fileextension in cpp_extensions:
names = _find_boost_tests(blame_lines,
names,
source)
if fileextension in python_extensions:
names = find_nose_tests(blame_lines, names, source)
names = _find_nose_tests(blame_lines,
names,
source)
except:
'Could not open file: ' + absfile
return names

if __name__ == "__main__":

def _main():
parser = argparse.ArgumentParser()
parser.add_argument('-d',
'--directory',
Expand All @@ -151,9 +231,18 @@ def find_git_status(directory, xctestsuperclasses):
help='A comma separated list of XCTest super classes',
required=False,
default='')
parser.add_argument('-v',
'--version',
help='Prints the version of testing game',
required=False,
default=False,
action='store_true')
args = parser.parse_args()
if args.version:
print 'testing game version 1.0.0'
return
xctest_superclasses = args.xctestsuperclasses.replace(' ', '').split(',')
names = find_git_status(args.directory, xctest_superclasses)
names = _find_git_status(args.directory, xctest_superclasses)
total_tests = 0
for name in names:
total_tests += names[name]
Expand All @@ -167,3 +256,6 @@ def find_git_status(directory, xctestsuperclasses):
'n': t[0],
't': t[1],
'p': percentage}

if __name__ == "__main__":
_main()
12 changes: 12 additions & 0 deletions testinggame/tests/ExampleTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.spotify.thing;

public class ExampleTest {
@Before
public void setUp() throws Exception {
}

@Test
public void testExample() {
Assert.assertEquals(0, 0);
}
}
24 changes: 24 additions & 0 deletions testinggame/tests/SPTTestExampleTest.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#import <SpotifyTestUtils/SPTTestCase.h>

@interface SPTTestExampleTest : SPTTestCase

@end

@implementation SPTTestExampleTest

- (void)setUp
{
[super setUp];
}

- (void)tearDown
{
[super tearDown];
}

- (void)testExample
{
XCTAssertEqual(0, 0);
}

@end
28 changes: 28 additions & 0 deletions testinggame/tests/XCTestExampleTest.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#import <XCTest/XCTestCase.h>

#include <string>

@interface XCTestExampleTest : XCTestCase

@end

@implementation XCTestExampleTest

- (void)setUp
{
[super setUp];
}

- (void)tearDown
{
[super tearDown];
}

- (void)testExample
{
std::string string1("thing");
std::string string2("thing");
XCTAssertEqual(string1, string2)
}

@end
12 changes: 12 additions & 0 deletions testinggame/tests/boost_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#include <boost/optional.hpp>
#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_SUITE(spotify)
BOOST_AUTO_TEST_SUITE(testinggame)

BOOST_AUTO_TEST_CASE(BoostTestExample) {
BOOST_CHECK_EQUAL(0, 0);
}

BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE_END()
Loading

0 comments on commit b6c9108

Please sign in to comment.