Trace Bug which happends only sometimes in CI

霸气de小男生 提交于 2019-12-10 03:39:57

问题


I have a strange bug in python code which only happens sometimes in CI.

We can't reproduce it.

Where is the test code:

response=self.admin_client.post(url, post)
self.assertEqual(200, response.status_code, response)

Sometimes we get a 302 which happens since the form gets saved.

My idea to debug this:

with some_magic_trace.trace() as trace:
    response=self.admin_client.post(url, post)
    self.assertEqual(200, response.status_code, trace)

The trace should contain the python lines (filename, line offset, line as string) executed by the interpreter.

How to implement some_magic_trace.trace()?


回答1:


The trace module gives you a very simple solution (different from what you are asking for, but simple enough to have a try.)

from trace import Trace

tracer = Trace()
response = tracer.runfunc(self.admin_client.post, url, post)
self.assertEqual(200, response.status_code, response)

A more complex solution that entails creating a context manager that saves the trace and prints it only on exceptions, requires the use of sys.settrace. Just a template for your own implementation could be:

class MyTracer():

    def __init__(self):
        self.trace = None

    def newscope(self, frame, event, arg):
        ## real work should be done here, just minimal example
        self.trace.append((frame, event, arg))
        return None

    def pprint(self):
        ## real pretty printing of trace info should be done here
        print(self.trace)

    def __enter__(self):
        self.trace = []
        sys.settrace(self.newscope)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        sys.settrace(None)
        if exc_type is not None:
            self.pprint()
            ## print some info gathered from exc_type, exc_val, exc_tb

Then you can:

with MyTracer():
    response=self.admin_client.post(url, post)
    self.assertEqual(200, response.status_code, response)

The idea is that a MyTracer instance has a tracer method newscope that saves some useful info in self.trace. On an abnormal exit from the context the pprint method is called; on a normal exit the trace info is discarded.

Most of the work has to be done in the tracing method newscope. Some concrete examples of tracing functions can be found here.




回答2:


Note: Stefano M's solution is the correct answer since fits perfectly with StackOverflow standards

For those unfamiliar with working with Frame/traceback objects I have provided a working Tracker class built from Stefano M's MyTracer template:

import sys,traceback

def printf(*stuff,sep=" ",end="\n",file=sys.stdout):
    file.write(sep.join(stuff)+end)#for backward compatability with python2

class Tracker:
    def __init__(self,out_file = sys.stdout):
        assert out_file.writable(),"need to open a file for writing"
        self.out = out_file
        self.last_frame = None

    def __enter__(self):
        self.old_trace = sys.gettrace()
        sys.settrace(self.newscope)

    def __exit__(self,etype,value,tb):
        sys.settrace(self.old_trace)
        self.finish_previous()
        if tb:
            traceback.printf_exception(etype,value,tb,file=self.out)
        else:
            printf("exit status normal",file=self.out)


    def newscope(self,f,e,ar):
        self.finish_previous(f)

        global_vars = f.f_globals
        for name,module in sys.modules.items():
            if global_vars is vars(module):
                file_name = name
                break
        else:
            file_name = "??"
            module = None

        printf(self._format_module(file_name,f.f_lineno),file=self.out)

        if module:
            printf(self._read_line_of_file(module,f.f_lineno),file=self.out)


    @staticmethod
    def _format_module(file_name,line):
        return "{}, line:{}".format(file_name,line)

    @staticmethod
    def _read_line_of_file(module,line):
        try:
            with open(module.__file__,"r") as FILE:
                line = list(FILE)[line]
                return line
        except Exception as e:
            return "ERROR WHEN READING FILE: %r"%e

    @staticmethod
    def _local_vars(frame):
        return "locals:\n    "+"\n    ".join("{} = {!r}".format(*pair) for pair in frame.f_locals.items())

    def finish_previous(self,new_frame=None):
        if self.last_frame:
            printf(self._local_vars(self.last_frame),end="\n\n\n",file=self.out)
        self.last_frame = new_frame

Then it can be used like this for displaying output to standard output:

with Tracker():
    response=self.admin_client.post(url, post)
    self.assertEqual(200, response.status_code, trace)

or to send output to a file you can do this:

with open("log.txt","w") as f, Tracker(f):
    response=self.admin_client.post(url, post)
    self.assertEqual(200, response.status_code, trace)

hope that can give you immediate results but you would still be much better off to implement the template by Stefano M yourself since the information you are looking for is likely different then what I would want displayed.




回答3:


To use trace.Trace with the with context instead of Trace.runfunc you can use:

import trace

class ContextTrace(trace.Trace):
    def __enter__(self):
        if not self.donothing:
            sys.settrace(self.globaltrace)
        return self

    def __exit__(self,*others):
        if not self.donothing:
            sys.settrace(None)


来源:https://stackoverflow.com/questions/34872918/trace-bug-which-happends-only-sometimes-in-ci

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!