"""Run the EL01 lab tests one lab step at a time.From the eng-lab folder, with the venv's Python: python labs/el01_hover/check.py every step (1-9) python labs/el01_hover/check.py 3 steps 1-3 only; use this while you workA step "passes" only when all of its tests pass. When one fails, read the test'sname and body first (tests/test_*.py shows the exact inputs and the arithmeticit expected), re-read that lab step in EL01, and climb the step's hint ladderone rung at a time."""importargparseimportsysimportunittestfrompathlibimportPathHERE=Path(__file__).resolve().parentSTEP_NAMES={1:"weight",2:"Newton's second law",3:"thrust-to-weight",4:"energy and flight time",5:"mass budget",6:"the arm (CAD)",7:"requirements",8:"phone accelerometer",9:"tinker log",}MODULE_STEPS={"test_hover":{1,2,3},"test_energy":{4},"test_massbudget":{5},"test_arm":{6},"test_requirements":{7},"test_kinematics":{8},"test_tinker":{9},}defstep_of(test):"""Step number from the test's class name (Step3ThrustToWeight -> 3)."""test=getattr(test,"test_case",test)# unwrap a failing subTestname=type(test).__name__ifname=="_ErrorHolder":# setUpClass failed: "setUpClass (test_arm.Step6Arm)"name=test.description.rsplit(".",1)[-1].rstrip(")")ifname.startswith("Step")andname[4:5].isdigit():returnint(name[4])returnNonedefflatten(suite):foriteminsuite:ifisinstance(item,unittest.TestSuite):yield fromflatten(item)else:yielditemdefphone_status():"""Report (not test) whether the physical test's prediction was written before its data."""root=HERE.parents[1]pred=HERE/"predictions.toml"data_dir=root/"data"/"tests"/"el01_phone"data=sorted(data_dir.glob("*.csv"))ifdata_dir.exists()else[]ifnotdata:return"no phone data yet (data/tests/el01_phone/*.csv)"ifnotpred.exists():return"phone data found, but no predictions.toml: write your prediction BEFORE measuring"first=min(p.stat().st_mtimeforpindata)return("prediction written before the data: yes"ifpred.stat().st_mtime<firstelse"prediction file is newer than the data: was it edited after measuring?")defmain():forstreamin(sys.stdout,sys.stderr):try:stream.reconfigure(encoding="utf-8",errors="replace")except(AttributeError,ValueError):passparser=argparse.ArgumentParser(description="Run the EL01 lab tests by step.")parser.add_argument("step",nargs="?",type=int,default=9,help="run steps 1..STEP (default 9)")args=parser.parse_args()ifnot1<=args.step<=9:parser.error("step must be between 1 and 9")wanted=set(range(1,args.step+1))sys.path.insert(0,str(HERE/"tests"))loader=unittest.TestLoader()suite=unittest.TestSuite()formodule,stepsinMODULE_STEPS.items():ifnotsteps&wanted:continuefortestinflatten(loader.loadTestsFromName(module)):step=step_of(test)ifstepisNoneorstepinwanted:# None: an import error, always show itsuite.addTest(test)print(f"EL01 lab: running steps 1-{args.step}\n")result=unittest.TextTestRunner(verbosity=2,stream=sys.stdout).run(suite)broken={}fortest,traceinresult.failures+result.errors:step=step_of(test)or"import"broken.setdefault(step,[]).append((test,"NotImplementedError"intrace))print("\nSummary")forstepinsorted(wanted):ifstepinbroken:tests=broken[step]ifall(not_writtenfor_,not_writtenintests):status="not written yet"else:first=next(tfort,nwintestsifnotnw)first=getattr(first,"test_case",first)label=(first.descriptioniftype(first).__name__=="_ErrorHolder"elsef"{type(first).__name__}.{first._testMethodName}")status=f"FAIL first failing test: {label}"else:status="pass"print(f" step {step} ({STEP_NAMES[step]}): {status}")if"import"inbroken:print(" A test file could not import your code. Read the traceback above first.")if8inwanted:print(f" phone test: {phone_status()}")print(f"\n{result.testsRun} tests run, {len(result.failures)} failures, "f"{len(result.errors)} errors, {len(result.skipped)} skipped")sys.exit(0ifresult.wasSuccessful()else1)if__name__=="__main__":main()