Issue #178 new

Coverage to support custom file formats

sebatello
created an issue

Hi! First I want to thank you for this awesome tool!.

I've added some small changes to this lib in order to support custom formats as coverage file output. The default settings will use pickle so this should keep backwards compatibility.

I'm trying to use your tool as a plugin for a distributed automation framework I'm developing for the company I work for. In my scenario test cases are distributed among different worker services which can be running different python versions, coverage can be enabled and all the coverage data sent by each of these workers has to be combined to generate a single report at the server's side. The issue I ran into is that the way pickle works changes among different python versions, so sometimes the data sent can't be unpickled if the worker that submitted it has a different version of python.

What I did was create a Formatter interface with two methods (load and dump which must be redefined by subclasses). Now data.py uses this interface instead of pickle to read and write files, the default formatter is a PickleFormatter that keeps the current behaviour. I've added a JSONFormatter that stores the coverage data in json format (the one I'll use in my scenario).

Besides, a user might easily define her custom format (e.g. if she wants to convert the data to the xml formats used by some java tools).

Some examples:

-------------------------

DEFAULT CASE: PICKLE

-------------------------

import coverage cov = coverage.coverage(data_file="test") cov.start() ... cov.stop() cov.save()

-------------------------

SAVE AS JSON

-------------------------

import coverage from coverage.formatter import JSONFormatter jsonf = JSONFormatter() cov = coverage.coverage(data_file="json_test", formatter=jsonf) cov.start() ... cov.combine() cov.stop() cov.save()

-------------------------

DEFINE A CUSTOM FORMAT

-------------------------

import coverage from coverage.formatter import Formatter

class SomeXMLFormat(Formatter): def load(fobject): etree = ElementTree() etree.parse(fobject) .... parse and transform to dict like .... {'collector': 'coverage vX.Y.Z',
..... 'lines': {'file1': [17,23,45], 'file2': [1,2,3], ... } } return data_dict

def dump(data, fobject): ... transform data dict to xml fobject.write(xml)

cform = SomeXMLFormat() cov = coverage.coverage(data_file="json_test", formatter=cform) cov.start() ... cov.combine() cov.stop() cov.save()

I'm attaching a zip with a new file (formatter.py) and the modified files (data.py, control.py, and backward.py). I'm not sending a .patch file because I'm not familiarized with mercurial and I don't know how to create it.

I hope you can implement this changes!

Thanks!

Sebastián Tello

PS: With these changes coverage still depends on simple file storage. I also wrote a prototype to use custom datastores that aren't necessarily single files but you'll be able to send and get data from sockets, web services, pyro objects, databases, etc. But this prototype requires more changes in the existing code (e.g. the elimination of filename). Let me know if you are interested on it.

Comments (2)

  1. sebatello reporter

    Oops, the examples looked awful, here they are again with the code wiki formatting.

    #Some examples:
    #-------------------------
    #DEFAULT CASE: PICKLE
    #-------------------------
    import coverage
    cov = coverage.coverage(data_file="test")
    cov.start()
    #...
    cov.stop()
    cov.save()
    
    #-------------------------
    #SAVE AS JSON
    #-------------------------
    import coverage
    from coverage.formatter import JSONFormatter
    jsonf = JSONFormatter()
    cov = coverage.coverage(data_file="json_test", formatter=jsonf)
    cov.start()
    # ...
    cov.combine()
    cov.stop()
    cov.save()
    
    #-------------------------
    #DEFINE A CUSTOM FORMAT
    #-------------------------
    import coverage
    from coverage.formatter import Formatter
    
    class SomeXMLFormat(Formatter):
       def load(fobject):
         etree = ElementTree()
         etree.parse(fobject)
         #.... parse and transform to dict like
         #.... {'collector': 'coverage vX.Y.Z',  
         #.....  'lines': {'file1': [17,23,45], 'file2': [1,2,3], ... } }
         return data_dict
    
       def dump(data, fobject):
         #... transform data dict to xml
         fobject.write(xml)
    
    
    cform = SomeXMLFormat()
    cov = coverage.coverage(data_file="json_test", formatter=cform)
    cov.start()
    #...
    cov.combine()
    cov.stop()
    cov.save()
    
  2. Log in to comment