@@ -21,6 +21,12 @@ class ASTNode(object):
2121 _BASH : str = 'bash'
2222 _SETUP : str = 'setup'
2323 _TEARDOWN : str = 'teardown'
24+ _BLOCK_CODE : str = 'block_code'
25+ _EXPECTATION : str = 'expectation'
26+ _STDOUT : str = 'stdout'
27+ _STDERR : str = 'stderr'
28+ _TABLE_CONTAINNS_DATA : str = 'table-contains-data'
29+ _REGEX : str = 'regex'
2430
2531 def __init__ (self , node : dict ):
2632 self .node = node
@@ -41,7 +47,19 @@ def get_text(self) -> str:
4147 return self .node .get ('raw' , '' ).strip ()
4248
4349 def is_executable (self ) -> bool :
44- return self .get_type () == 'block_code'
50+ return self .get_type () == self ._BLOCK_CODE
51+
52+ def is_expectation (self ) -> bool :
53+ return self .get_type () == self ._BLOCK_CODE and self ._EXPECTATION in self ._get_annotations ()
54+
55+ def get_expectation_metadata (self ) -> dict :
56+ return self .node .get ('attrs' , {}).get ('info' , '' ).split (' ' )
57+
58+ def _has_annotation (self , annotation : str ) -> List [str ]:
59+ return annotation in self ._get_annotations ()
60+
61+ def has_annotation (self , annotation : str ) -> bool :
62+ return self ._has_annotation (annotation )
4563
4664 def _get_annotations (self ) -> List [str ]:
4765 return self .node .get ('attrs' , {}).get ('info' , '' ).split (' ' )
@@ -81,6 +99,12 @@ def get_ordered(self) -> List[ASTNode]:
8199
82100 def expand (self , node : ASTNode ) -> str :
83101 return node .expand ()
102+
103+ def __str__ (self ):
104+ return json .dumps ([node .node for node in self ._ast ], indent = 2 )
105+
106+ def __repr__ (self ):
107+ return self .__str__ ()
84108
85109
86110
@@ -92,13 +116,58 @@ def parse_markdown_file(self, file_path: str, lang=None) -> MdAST:
92116 txt = f .read ()
93117 raw_list : List [dict ] = markdown (txt )
94118 return MdAST ([ASTNode (node ) for node in raw_list ])
119+
120+ class Expectation (object ):
121+
122+ _STDOUT_TABLE_CONTAINS_DATA : str = 'stdout-table-contains-data'
123+
124+ def __init__ (self , node : ASTNode ):
125+ self ._node : ASTNode = node
126+
127+ def _contains_nonempty_table (self , s : str ) -> bool :
128+ required_run_length : int = 5
129+ lines = s .split ('\n ' )
130+ print (f'lines: { lines } ' )
131+ if len (lines ) < required_run_length :
132+ return False
133+ run_length : int = 0
134+ for line in lines :
135+ if line .startswith ('|' ):
136+ run_length += 1
137+ if run_length == required_run_length :
138+ return True
139+ else :
140+ run_length = 0
141+ return False
142+
143+ def passes_stdout (self , actual_stdout : bytes ) -> bool :
144+ actual_stdout_str : str = actual_stdout .decode (sys .getdefaultencoding ())
145+ if self ._node .has_annotation (self ._STDOUT_TABLE_CONTAINS_DATA ):
146+ return self ._contains_nonempty_table (actual_stdout_str )
147+ return True
148+
149+ def passes_stderr (self , actual_stderr : bytes ) -> bool :
150+ return True
151+
152+ def __str__ (self ):
153+ return str (self ._node )
154+
155+ def __repr__ (self ):
156+ return self .__str__ ()
95157
96158class WorkloadDTO (object ):
97159
98- def __init__ (self , setup : str , in_session : List [str ], teardown : str ):
160+ def __init__ (
161+ self ,
162+ setup : str ,
163+ in_session : List [str ],
164+ teardown : str ,
165+ expectations : List [Expectation ]
166+ ):
99167 self ._setup = setup
100168 self ._in_session = in_session
101169 self ._teardown = teardown
170+ self ._expectations = expectations
102171
103172 def get_setup (self ) -> List [str ]:
104173 return self ._setup
@@ -109,8 +178,11 @@ def get_in_session(self) -> List[str]:
109178 def get_teardown (self ) -> List [str ]:
110179 return self ._teardown
111180
181+ def get_expectations (self ) -> List [Expectation ]:
182+ return self ._expectations
183+
112184 def __str__ (self ):
113- return f'Setup: { self ._setup } \n In Session: { self ._in_session } \n Teardown: { self ._teardown } '
185+ return f'Setup: { self ._setup } \n In Session: { self ._in_session } \n Teardown: { self ._teardown } \n Expectations: { self . _expectations } '
114186
115187 def __repr__ (self ):
116188 return self .__str__ ()
@@ -140,8 +212,12 @@ def orchestrate(self, file_path: str) -> WorkloadDTO:
140212 setup_str : str = f'cd { _REPOSITORY_ROOT_PATH } ;\n '
141213 in_session_commands : List [str ] = []
142214 teardown_str : str = f'cd { _REPOSITORY_ROOT_PATH } ;\n '
215+ expectations : List [Expectation ] = []
143216 for node in ast .get_ordered ():
144- if node .is_executable ():
217+ if node .is_expectation ():
218+ expectations .append (Expectation (node ))
219+ continue
220+ elif node .is_executable ():
145221 if node .is_setup ():
146222 if setup_count < self ._max_setup_blocks :
147223 setup_str += ast .expand (node )
@@ -161,7 +237,7 @@ def orchestrate(self, file_path: str) -> WorkloadDTO:
161237 invocation_count += 1
162238 else :
163239 raise KeyError (f'Maximum invocation blocks exceeded: { self ._max_invocations_blocks } ' )
164- return WorkloadDTO (setup_str , in_session_commands , teardown_str )
240+ return WorkloadDTO (setup_str , in_session_commands , teardown_str , expectations )
165241
166242class SimpleE2E (object ):
167243
@@ -194,6 +270,11 @@ def run(self) -> Tuple[bytes, bytes]:
194270 stdout_bytes , stderr_bytes = e2e .run ()
195271 print (stdout_bytes .decode (sys .getdefaultencoding ()))
196272 print (stderr_bytes .decode (sys .getdefaultencoding ()))
273+ for expectation in workload_dto .get_expectations ():
274+ print (f'Expectation: { expectation } ' )
275+ print (f'Passes stdout: { expectation .passes_stdout (stdout_bytes )} ' )
276+ print (f'Passes stderr: { expectation .passes_stderr (stderr_bytes )} ' )
277+ print ('---' )
197278
198279
199280
0 commit comments