docs: def challenge(self,
  	              shutit,
  	              task_desc,
  	              expect=None,
  	              hints=None,
  	              congratulations='OK',
  	              failed='FAILED',
  	              expect_type='exact',
  	              challenge_type='command',
  	              timeout=None,
  	              check_exit=None,
  	              fail_on_empty_before=True,
  	              record_command=True,
  	              exit_values=None,
  	              echo=True,
  	              escape=False,
  	              pause=1,
  	              loglevel=logging.DEBUG,
  	              follow_on_context=None,
  	              difficulty=1.0,
  	              reduction_per_minute=0.2,
  	              reduction_per_reset=0,
  	              reduction_per_hint=0.5,
  	              grace_period=30,
  	              new_stage=True,
  	              final_stage=False,
  	              num_stages=None):
  		"""Set the user a task to complete, success being determined by matching the output.
  
  		Either pass in regexp(s) desired from the output as a string or a list, or an md5sum of the output wanted.
  
  		@param follow_on_context     On success, move to this context. A dict of information about that context.
  		                             context              = the type of context, eg docker, bash
  		                             ok_container_name    = if passed, send user to this container
  		                             reset_container_name = if resetting, send user to this container
  		@param challenge_type        Behaviour of challenge made to user
  		                             command = check for output of single command
  		                             golf    = user gets a pause point, and when leaving, command follow_on_context['check_command'] is run to check the output
  		"""
  		shutit = self.shutit
  		if new_stage and shutit.build['exam_object']:
  			if num_stages is None:
  				num_stages = shutit.build['exam_object'].num_stages
  			elif shutit.build['exam_object'].num_stages < 1:
  				shutit.build['exam_object'].num_stages = num_stages
  			elif shutit.build['exam_object'].num_stages > 0:
  				shutit.fail('Error! num_stages passed in should be None if already set in exam object (ie > 0)') # pragma: no cover
  			curr_stage = str(shutit.build['exam_object'].curr_stage)
  			if num_stages > 0:
  				task_desc = 80*'*' + '\n' + '* QUESTION ' + str(curr_stage) + '/' + str(num_stages) + '\n' + 80*'*' + '\n' + task_desc
  			else:
  				task_desc = 80*'*' + '\n' + '* QUESTION \n' + 80*'*' + '\n' + task_desc
  			shutit.build['exam_object'].new_stage(difficulty=difficulty,
  			                                      reduction_per_minute=reduction_per_minute,
  			                                      reduction_per_reset=reduction_per_reset,
  			                                      reduction_per_hint=reduction_per_hint,
  			                                      grace_period=grace_period)
  			# If this is an exam, then remove history.
  			self.send(ShutItSendSpec(self,
  			                         send=' history -c',
  			                         check_exit=False,
  			                         ignore_background=True))
  		# don't catch CTRL-C, pass it through.
  		shutit.build['ctrlc_passthrough'] = True
  		preserve_newline                  = False
  		skipped                           = False
  		if expect_type == 'regexp':
  			if isinstance(expect, str):
  				expect = [expect]
  			if not isinstance(expect, list):
  				shutit.fail('expect_regexps should be list') # pragma: no cover
  		elif expect_type == 'md5sum':
  			preserve_newline = True
  		elif expect_type == 'exact':
  			pass
  		else:
  			shutit.fail('Must pass either expect_regexps or md5sum in') # pragma: no cover
  		if hints:
  			shutit.build['pause_point_hints'] = hints
  		else:
  			shutit.build['pause_point_hints'] = []
  		if challenge_type == 'command':
  			help_text = shutit_util.colorise('32','''\nType 'help' or 'h' to get a hint, 'exit' to skip, 'shutitreset' to reset state.''')
  			ok = False
  			while not ok:
  				shutit.log(shutit_util.colorise('32','''\nChallenge!'''),transient=True, level=logging.INFO)
  				if hints:
  					shutit.log(shutit_util.colorise('32',help_text),transient=True, level=logging.INFO)
  				time.sleep(pause)
  				# TODO: bash path completion
  				send = shutit_util.get_input(task_desc + ' => ',color='31')
  				if not send or send.strip() == '':
  					continue
  				if send in ('help','h'):
  					if hints:
  						shutit.log(help_text,transient=True, level=logging.CRITICAL)
  						shutit.log(shutit_util.colorise('32',hints.pop()),transient=True, level=logging.CRITICAL)
  					else:
  						shutit.log(help_text,transient=True, level=logging.CRITICAL)
  						shutit.log(shutit_util.colorise('32','No hints left, sorry! CTRL-g to reset state, CTRL-s to skip this step, CTRL-] to submit for checking'),transient=True, level=logging.CRITICAL)
  					time.sleep(pause)
  					continue
  				if send == 'shutitreset':
  					self._challenge_done(shutit, result='reset',follow_on_context=follow_on_context,final_stage=False)
  					continue
  				if send == 'shutitquit':
  					self._challenge_done(shutit, result='reset',follow_on_context=follow_on_context,final_stage=True)
  					shutit_global.shutit_global_object.handle_exit(exit_code=1)
  				if send == 'exit':
  					self._challenge_done(shutit, result='exited',follow_on_context=follow_on_context,final_stage=True)
  					shutit.build['pause_point_hints'] = []
  					return True
  				output = self.send_and_get_output(send,
  				                                  timeout=timeout,
  				                                  retry=1,
  				                                  record_command=record_command,
  				                                  echo=echo,
  				                                  loglevel=loglevel,
  				                                  fail_on_empty_before=False,
  				                                  preserve_newline=preserve_newline)
  				md5sum_output = md5(output).hexdigest()
  				shutit.log('output: ' + output + ' is md5sum: ' + md5sum_output, level=logging.DEBUG)
  				if expect_type == 'md5sum':
  					output = md5sum_output
  					if output == expect:
  						ok = True
  				elif expect_type == 'exact':
  					if output == expect:
  						ok = True
  				elif expect_type == 'regexp':
  					for regexp in expect:
  						if shutit.match_string(output, regexp):
  							ok = True
  							break
  				if not ok and failed:
  					if shutit.build['exam_object']:
  						shutit.build['exam_object'].add_fail()
  						shutit.build['exam_object'].end_timer()
  					shutit.log('\n\n' + shutit_util.colorise('32','failed') + '\n',transient=True, level=logging.CRITICAL)
  					self._challenge_done(shutit, result='failed',final_stage=final_stage)
  					continue
  		elif challenge_type == 'golf':
  			# pause, and when done, it checks your working based on check_command.
  			ok = False
  			# hints
  			if hints:
  				# TODO: debug this, it doesn't work!
  				task_desc_new = task_desc + '\r\n\r\nHit CTRL-h for help, CTRL-g to reset state, CTRL-s to skip, CTRL-] to submit for checking'
  			else:
  				task_desc_new = '\r\n' + task_desc
  			while not ok:
  				if shutit.build['exam_object'] and new_stage:
  					shutit.build['exam_object'].start_timer()
  					# Set the new_stage to False, as we're in a loop that doesn't need to mark a new state.
  					new_stage = False
  				self.pause_point(shutit_util.colorise('31',task_desc_new),color='31')
  				if shutit_global.shutit_global_object.signal_id == 8:
  					if shutit.build['exam_object']:
  						shutit.build['exam_object'].add_hint()
  					if shutit.build['pause_point_hints']:
  						shutit.log(shutit_util.colorise('31','\r\n========= HINT ==========\r\n\r\n' + shutit.build['pause_point_hints'].pop(0)),transient=True, level=logging.CRITICAL)
  					else:
  						shutit.log(shutit_util.colorise('31','\r\n\r\n' + 'No hints available!'),transient=True, level=logging.CRITICAL)
  					time.sleep(1)
  					# clear the signal
  					shutit_global.shutit_global_object.signal_id = 0
  					continue
  				elif shutit_global.shutit_global_object.signal_id == 17:
  					# clear the signal and ignore CTRL-q
  					shutit_global.shutit_global_object.signal_id = 0
  					continue
  				elif shutit_global.shutit_global_object.signal_id == 7:
  					if shutit.build['exam_object']:
  						shutit.build['exam_object'].add_reset()
  					shutit.log(shutit_util.colorise('31','\r\n========= RESETTING STATE ==========\r\n\r\n'),transient=True, level=logging.CRITICAL)
  					self._challenge_done(shutit, result='reset', follow_on_context=follow_on_context,final_stage=False)
  					# clear the signal
  					shutit_global.shutit_global_object.signal_id = 0
  					# Get the new target child, which is the new 'self'
  					target_child = shutit.get_shutit_pexpect_session_from_id('target_child')
  					return target_child.challenge(
  						shutit,
  						task_desc=task_desc,
  						expect=expect,
  						hints=hints,
  						congratulations=congratulations,
  						failed=failed,
  						expect_type=expect_type,
  						challenge_type=challenge_type,
  						timeout=timeout,
  						check_exit=check_exit,
  						fail_on_empty_before=fail_on_empty_before,
  						record_command=record_command,
  						exit_values=exit_values,
  						echo=echo,
  						escape=escape,
  						pause=pause,
  						loglevel=loglevel,
  						follow_on_context=follow_on_context,
  						new_stage=False
  					)
  				elif shutit_global.shutit_global_object.signal_id == 19:
  					if shutit.build['exam_object']:
  						shutit.build['exam_object'].add_skip()
  						shutit.build['exam_object'].end_timer()
  					# Clear the signal.
  					shutit_global.shutit_global_object.signal_id = 0
  					# Skip test.
  					shutit.log('\r\nTest skipped... please wait', level=logging.CRITICAL,transient=True)
  					skipped=True
  					self._challenge_done(shutit, result='skipped',follow_on_context=follow_on_context,skipped=True,final_stage=final_stage)
  					return True
  				elif shutit_global.shutit_global_object.signal_id == 29:
  					# Clear the signal
  					shutit_global.shutit_global_object.signal_id = 0
  				else:
  					shutit.log('Signal not handled: ' + str(shutit_global.shutit_global_object.signal_id), level=logging.CRITICAL,transient=True)
  
  				shutit.log('\r\nState submitted, checking your work...', level=logging.CRITICAL,transient=True)
  				check_command = follow_on_context.get('check_command')
  				output = self.send_and_get_output(check_command,
  				                                  timeout=timeout,
  				                                  retry=1,
  				                                  record_command=record_command,
  				                                  echo=False,
  				                                  loglevel=loglevel,
  				                                  fail_on_empty_before=False,
  				                                  preserve_newline=preserve_newline)
  				shutit.log('output: ' + output, level=logging.DEBUG)
  				md5sum_output = md5(output).hexdigest()
  				if expect_type == 'md5sum':
  					shutit.log('output: ' + output + ' is md5sum: ' + md5sum_output, level=logging.DEBUG)
  					output = md5sum_output
  					if output == expect:
  						ok = True
  				elif expect_type == 'exact':
  					if output == expect:
  						ok = True
  				elif expect_type == 'regexp':
  					for regexp in expect:
  						if shutit.match_string(output,regexp):
  							ok = True
  							break
  				if not ok and failed:
  					shutit.log('\r\n\n' + shutit_util.colorise('31','Failed! CTRL-g to reset state, CTRL-h for a hint, CTRL-] to submit for checking') + '\n',transient=True, level=logging.CRITICAL)
  					# No second chances if exam!
  					if shutit.build['exam_object']:
  						shutit.build['exam_object'].add_fail()
  						shutit.build['exam_object'].end_timer()
  						self._challenge_done(shutit, result='failed_test',follow_on_context=follow_on_context,final_stage=final_stage)
  						return False
  					else:
  						continue
  		else:
  			shutit.fail('Challenge type: ' + challenge_type + ' not supported') # pragma: no cover
  		self._challenge_done(shutit,
  		                     result='ok',
  		                     follow_on_context=follow_on_context,
  		                     congratulations=congratulations,
  		                     skipped=skipped,
  		                     final_stage=final_stage)
  		if shutit.build['exam_object']:
  			shutit.build['exam_object'].add_ok()
  			shutit.build['exam_object'].end_timer()
  		# Tidy up hints
  		shutit.build['pause_point_hints'] = []
  		return True