Trace Bug, which is sometimes found only in CI

I have a weird bug in python code that sometimes only happens in CI.

We cannot reproduce it.

Where is the test code located:

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

Sometimes we get 302, which happens from the moment the form is saved.

My idea is 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 must contain python lines (file name, line offset, line as line) executed by the interpreter.

How to implement some_magic_trace.trace() ?

+7
python debugging trace
source share
3 answers

The trace module gives you a very simple solution (different from what you are asking for, but simple enough to 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, which entails the creation of a context manager that saves tracing 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 the MyTracer instance has a newscope trace method that stores useful information in self.trace . When abnormally exiting the context, the pprint method is pprint ; during normal output, trace information is discarded.

Most of the work needs to be done in the newscope trace newscope . Some specific examples of trace functions can be found here .

+8
source share

Note. Stefano M is the right answer because it is perfect for StackOverflow standards

For those who are not familiar with working with Frame / traceback objects, I have provided a working Tracker class built from the Stefano M 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 to map the output to standard output:

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

or send the 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) 

I hope you can get immediate results, but it will still be much better for you to implement the Stefano M template yourself, since the information you are looking for is most likely different from what I would like to display.

+5
source share

To use trace.Trace instead of 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) 
+1
source share

All Articles