# A pyexpect-based thing exercising some of tapsh's functions.
#
# Maybe we should define unit tests in addition to this, but, really,
# the tricky stuff should be tested in the DaCHS votable library anyway.
#
# There should be no SAMP client running on the system when running this
# script.
#
# To watch the interaction, uncomment child.logfile = sys.stdout below.

import contextlib
import os
import re
import sys
import time
from cStringIO import StringIO

import pexpect

from gavo import utils


class ExpectingTest(object):
	"""A test dialog based on expect.

	You define a dialog in the form of (input, pattern) pairs, and you
	can override the verify(output) method to inspect the full
	output of tested program.

	Call run(child) with a pexpect.spawn object child to actually run
	the test.

	Since we want to carry over state from one test to the next to
	avoid huge tests, there is a defined order of the tests, defined
	by the integer seqNo.  Tests are executed by increasing seqNo.
	"""
	dialog = []
	seqNo = None

	def verify(self, transcript):
		print "Reply was:", repr(transcript)

	@contextlib.contextmanager
	def _writesLog(self, child):
		child.logfile_read = log = StringIO()
		try:
			yield log
		finally:
			child.write("")
			child.logfile_read = None

	def run(self, child):
		with self._writesLog(child) as log:
			for inputData, outputPattern in self.dialog:
				child.send(inputData)
				child.expect_exact(outputPattern)
		self.verify(log.getvalue())


class ServerAddTest(ExpectingTest):
	seqNo = 5
	dialog = [
		("addserver gaooba http://irrele.vant\n", "tapsh>"),
		("server gao\t", "oba"),
		("\n", "tapsh>"),
		("server\n", "tapsh>")]
	
	def verify(self, output):
		assert re.search("server\s*gaooba", output)


class UpdateServerTest(ExpectingTest):
	seqNo = 10
	dialog = [
		("refreshs\t", "ervers"),
		("\n", "tapsh>")]
	
	def verify(self, output):
		assert re.search(r"Refreshing TAP.*\.\.\. done", output)


class PurgeTest(ExpectingTest):
	seqNo = 20
	dialog = [
		("purge all\n", "tapsh>"),
		("ls\n", "tapsh>"),]

	def verify(self, output):
		# make sure all output is gone
		assert re.search(r"ls\s+tapsh>", output)


class SelectDCServer(ExpectingTest):
	seqNo = 30
	dialog = [
		("server ivo://org.gavo\t", "dc"),
		("\n", "tapsh>")]

	def verify(self, output):
		assert("dc/tap" in output)


class CreateJob(ExpectingTest):
	seqNo = 40
	dialog = [
		("select top 10 * from ppmx.\t", "data"),
		("\n", "tapsh>"),
		("nick fong\n", "tapsh>"),
		("job fo\t", "ng"),
		("\n", "tapsh>")]
	
	def verify(self, output):
		assert "Job nicked fong" in output
		assert "phase PENDING" in output


class SetPar(ExpectingTest):
	seqNo = 45
	dialog = [
		("setPar query select top 5 * from ppmx.data\n", "tapsh>"),
		("job\n", "tapsh>")]

	def verify(self, output):
		assert re.search(r"------\s*select top 5 \* from", output)


class ChangeLimit(ExpectingTest):
	seqNo = 50
	dialog = [
		("limit 20\n", "tapsh>"),
		("job\n", "tapsh>")]
	
	def verify(self, output):
		assert "time limit 20" in output


class HelpForKeepFor(ExpectingTest):
	seqNo = 60
	dialog = [
		("help keepfor\n", "tapsh>"),]
	
	def verify(self, output):
		assert "around for a limited period" in output


class ChangeDestruction(ExpectingTest):
	seqNo = 70
	dialog = [
		("keepf\t", "or"),
		("30\n", "tapsh>"),]
	
	def verify(self, output):
		assert "Destruction of fong scheduled"


class DumpErrorMessage(ExpectingTest):
	seqNo = 75
	dialog = [("dump\n", "tapsh>")]
	
	def verify(self, output):
		assert "*** Results can only be dumped on COMPLETED jobs"
		

class RunJob(ExpectingTest):
	seqNo = 80
	dialog = [
		("start\n", "tapsh>"),
		("job\n", "tapsh>"),]
	
	def verify(self, output):
		assert ("phase QUEUED" in output or "phase EXECUTING" in output
			or "phase COMPLETED" in output or "phase UNKNOWN" in output)


class WaitForCompletion(ExpectingTest):
	seqNo = 90

	def run(self, child):
		with self._writesLog(child) as log:
			for i in range(20):
				child.send("job\n")
				child.expect("phase ([A-Z]*)")
				if child.match.group(1) in ["COMPLETED", "ERROR"]:
					break
				time.sleep(1)
			else:
				assert False, "Job hung"
		child.expect("tapsh>")


class SAMPSendNot(ExpectingTest):
	seqNo = 100
	dialog = [
		("send to uglyclient\n", "tapsh>"),
	]

	def verify(self, output):
		assert "SAMP client 'uglyclient' not found" in output


class CreateSecondJob(ExpectingTest):
	seqNo = 110
	dialog = [
#		("select * from (select top 4 * from rave.main) as q"
		("select qual from (select top 4 * from rave.main) as q"
			" join tap_upload.upl on (raj2000=alphaFloat) order by localid\n", 
				"tapsh>"),
		("nick newone\n", "tapsh>"),]

	def verify(self, output):
		pass


class UploadResult(ExpectingTest):
	seqNo = 120
	dialog = [
		("upload res\t", "ult"),
		("fong \t", "as"),
		("upl\n", "tapsh>")]

	def verify(self, output):
		pass


class RunOtherJob(ExpectingTest):
	seqNo = 130
	dialog = [
		("job fong\n", "tapsh>"),
		("run new\t", "one"),
		("\n", "tapsh>")]
	
	def verify(self, output):
		assert "Job nicked fong" in output


class EnsureOtherCompletion(ExpectingTest):
	seqNo = 140

	dialog = [
		("job newone\n", "tapsh>")]
	
	def verify(self, output):
		assert "phase COMPLETED" in output


class SaveVOT(ExpectingTest):
	seqNo = 150

	dialog = [('save "zw.vot"\n', "tapsh>")]

	def verify(self, output):
		try:
			with open("zw.vot") as f:
				assert "c -- cosmic ray pollution" in f.read()
		finally:
			os.unlink("zw.vot")


class DumpMakesTable(ExpectingTest):
	seqNo = 155

	dialog = [('dump fong\n', 'tapsh>')]

	def verify(self, output):
		assert "alphafloat     deltafloat" in output
		assert "False         S       G  " in output


class LsShows(ExpectingTest):
	seqNo = 160

	dialog = [("ls\n", "tapsh>")]

	def verify(self, output):
		assert re.search("fong[^\n]*dc.zah[^\n]* COMPLETED", output)
		assert re.search("newone[^\n]*dc.zah[^\n]* COMPLETED", output)


class PurgeDoesNotTouchCompleted(ExpectingTest):
	seqNo = 170

	dialog = [
		('purge\n', "tapsh>"),
		('ls\n', "tapsh>"),]

	def verify(self, output):
		assert re.search("fong[^\n]*dc.zah[^\n]* COMPLETED", output)
		assert re.search("newone[^\n]*dc.zah[^\n]* COMPLETED", output)


class RemoteErrorOutputTest(ExpectingTest):
	seqNo = 180

	dialog = [
		('select foo flom bar\n', 'tapsh>'),
		('nick KaputtnicK\n', 'tapsh>'),
		('run\n', 'tapsh>'),
		('error\n', 'tapsh>')]

	def verify(self, output):
		assert re.search(r'Could not parse your query: Expected "FROM"', output)


class PurgeFailed(ExpectingTest):
	seqNo = 190

	dialog = [
		('purge failed\n', "tapsh>"),
		('ls\n', "tapsh>")]
	
	def verify(self, output):
		assert "Deleting KaputtnicK" in output
		assert re.search("fong[^\n]*COMPLETED", output)


class ImmediateDump(ExpectingTest):
	seqNo = 1000
	dialog = [('select table_name from tap_schema.tables!\n', 'tapsh>')]

	def verify(self, output):
		assert("table_name" in output)
		assert("tap_schema.tables" in output)


class ReportRemoteSyntaxError(ExpectingTest):
	seqNo = 10000

	dialog = [
		('select * fom tap_schema.tables;\n', 'tapsh>'),
		('delete\n', 'tapsh>')]
	
	def verify(self, output):
		assert ('*** (remote:) Field query: Could not parse your query:'
			' Expected "FROM"' in output)


class ReportLocalSyntaxError(ExpectingTest):
	seqNo = 10010

	dialog = [
		('seect * from tap_schema.tables;\n', 'tapsh>')]
	
	def verify(self, output):
		assert "*** No command 'seect' known" in output


class ReportLocalNonExistingJob(ExpectingTest):
	seqNo = 10020

	dialog = [("job KnaKut\n", "tapsh>")]

	def verify(self, output):
		assert "*** No such job known: 'KnaKut'" in output


class ReportLocalNonExistingServer(ExpectingTest):
	seqNo = 10030

	dialog = [("server ivo://KnaKut\n", "tapsh>"),
		("server ivo://org.gavo.dc/tap\n", "tapsh>"),
	]

	def verify(self, output):
		assert "*** Unknown TAP server 'ivo://KnaKut'" in output


class ExecTest(ExpectingTest):
	seqNo = 10040

	dialog = [
		('exec "~/tap$sh\t\t', 'regression.sql" '),
		("\n", "tapsh>"),
		("run\n", "tapsh>"),
		("job\n", "tapsh>"),
		("delete\n", "tapsh>")]

	def run(self, child):
		scriptPath = os.path.expanduser("~/tap$shregression.sql")
		with open(scriptPath, "w") as f:
			f.write("select\n\ntop 4\n*\nfrom\n  ppmx.data\n")

		ExpectingTest.run(self, child)

		os.unlink(scriptPath)
	
	def verify(self, output):
		assert "phase COMPLETED" in output


class CleanUpAfterMyself(ExpectingTest):
	seqNo = 999999

	dialog = [("purge all\n", "tapsh>")]
	
	def verify(self, output):
		pass


def main(minSeq, maxSeq):
#	child = pexpect.spawn("java -jar ../makejar/tapsh.jar", timeout=60)
	child = pexpect.spawn("tapsh", timeout=20)
#	child.logfile = sys.stdout
	child.expect("tapsh>")
	toRun = sorted((t for t in
			utils.iterDerivedClasses(ExpectingTest, globals().values())
			if minSeq<=t.seqNo<=maxSeq), 
		key=lambda arg: arg.seqNo)
	try:
		for testClass in toRun:
			sys.stdout.write("%s..."%testClass.__name__)
			sys.stdout.flush()
			testClass().run(child)
			sys.stdout.write("\n")
	finally:
		child.send("quit")


if __name__=="__main__":
	minSeq = 0
	if len(sys.argv)>1:
		minSeq = int(sys.argv[1])
	maxSeq = 3000000
	if len(sys.argv)>2:
		maxSeq = int(sys.argv[2])

	main(minSeq, maxSeq)
