示例#1
0
void TestPlan::testVpmbMetricMultiLevelAir()
{
	char *cache = NULL;

	setupPrefsVpmb();
	prefs.unit_system = METRIC;
	prefs.units.length = units::METERS;

	struct diveplan testPlan = { 0 };
	setupPlanVpmbMultiLevelAir(&testPlan);
	setCurrentAppState("PlanDive");

	plan(&testPlan, &cache, 1, 0);

#if DEBUG
	free(displayed_dive.notes);
	displayed_dive.notes = NULL;
	save_dive(stdout, &displayed_dive);
#endif

	// print first ceiling
	printf("First ceiling %.1f m\n", (mbar_to_depth(first_ceiling_pressure.mbar, &displayed_dive) * 0.001));
	// check benchmark run time of 167 minutes, and known Subsurface runtime of 169 minutes
	QVERIFY(compareDecoTime(displayed_dive.dc.duration.seconds, 167u * 60u + 20u, 169u * 60u + 20u));
}
示例#2
0
void TestPlan::testImperial()
{
	char *cache = NULL;

	setupPrefs();
	prefs.unit_system = IMPERIAL;
	prefs.units.length = units::FEET;
	prefs.deco_mode = BUEHLMANN;

	struct diveplan testPlan = { 0 };
	setupPlan(&testPlan);

	plan(&testPlan, &cache, 1, 0);

#if DEBUG
	free(displayed_dive.notes);
	displayed_dive.notes = NULL;
	save_dive(stdout, &displayed_dive);
#endif

	// check first gas change to EAN36 at 33m
	struct event *ev = displayed_dive.dc.events;
	QVERIFY(ev != NULL);
	QCOMPARE(ev->gas.index, 1);
	QCOMPARE(ev->value, 36);
	QCOMPARE(get_depth_at_time(&displayed_dive.dc, ev->time.seconds), 33528);
	// check second gas change to Oxygen at 6m
	ev = ev->next;
	QVERIFY(ev != NULL);
	QCOMPARE(ev->gas.index, 2);
	QCOMPARE(ev->value, 100);
	QCOMPARE(get_depth_at_time(&displayed_dive.dc, ev->time.seconds), 6096);
	// check expected run time of 110 minutes
	QVERIFY(compareDecoTime(displayed_dive.dc.duration.seconds, 110u * 60u - 2u, 110u * 60u - 2u));
}
示例#3
0
void TestPlan::testVpmbMetric60m30minTx()
{
	char *cache = NULL;

	setupPrefsVpmb();
	prefs.unit_system = METRIC;
	prefs.units.length = units::METERS;

	struct diveplan testPlan = { 0 };
	setupPlanVpmb60m30minTx(&testPlan);
	setCurrentAppState("PlanDive");

	plan(&testPlan, &cache, 1, 0);

#if DEBUG
	free(displayed_dive.notes);
	displayed_dive.notes = NULL;
	save_dive(stdout, &displayed_dive);
#endif

	// print first ceiling
	printf("First ceiling %.1f m\n", (mbar_to_depth(first_ceiling_pressure.mbar, &displayed_dive) * 0.001));
	// check first gas change to EAN50 at 21m
	struct event *ev = displayed_dive.dc.events;
	QVERIFY(ev != NULL);
	QCOMPARE(ev->gas.index, 1);
	QCOMPARE(ev->value, 50);
	QCOMPARE(get_depth_at_time(&displayed_dive.dc, ev->time.seconds), 21000);
	// check benchmark run time of 89 minutes, and known Subsurface runtime of 89 minutes
	QVERIFY(compareDecoTime(displayed_dive.dc.duration.seconds, 89u * 60u + 20u, 89u * 60u + 20u));
}
示例#4
0
void TestPlan::testVpmbMetricMultiLevelAir()
{
	struct deco_state *cache = NULL;

	setupPrefsVpmb();
	prefs.unit_system = METRIC;
	prefs.units.length = units::METERS;

	struct diveplan testPlan = {};
	setupPlanVpmbMultiLevelAir(&testPlan);
	setAppState(ApplicationState::PlanDive);

	plan(&test_deco_state, &testPlan, &displayed_dive, 60, stoptable, &cache, 1, 0);

#if DEBUG
	free(displayed_dive.notes);
	displayed_dive.notes = NULL;
	save_dive(stdout, &displayed_dive, false);
#endif

	// check minimum gas result
	struct divedatapoint *dp = testPlan.dp;
	while (!dp->minimum_gas.mbar && dp->next)
		dp = dp->next;
	QCOMPARE(lrint(dp->minimum_gas.mbar / 1000.0), 101l);
	// print first ceiling
	printf("First ceiling %.1f m\n", (mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar, &displayed_dive) * 0.001));
	// check benchmark run time of 167 minutes, and known Subsurface runtime of 169 minutes
	QVERIFY(compareDecoTime(displayed_dive.dc.duration.seconds, 167u * 60u + 20u, 169u * 60u + 20u));
}
示例#5
0
void save_dives(const char *filename)
{
	int i;
	GList *trip = NULL;

	FILE *f = fopen(filename, "w");

	if (!f)
		return;

	/* Flush any edits of current dives back to the dives! */
	update_dive(current_dive);

	fprintf(f, "<dives>\n<program name='subsurface' version='%d'></program>\n", VERSION);

	/* save the trips */
	while ((trip = NEXT_TRIP(trip)) != NULL)
		save_trip(f, trip->data);

	/* save the dives */
	for (i = 0; i < dive_table.nr; i++)
		save_dive(f, get_dive(i));
	fprintf(f, "</dives>\n");
	fclose(f);
}
示例#6
0
void TestPlan::testMultipleGases()
{
	struct deco_state *cache = NULL;

	setupPrefsVpmb();
	prefs.unit_system = METRIC;
	prefs.units.length = units::METERS;

	struct diveplan testPlan = {};

	setupPlanSeveralGases(&testPlan);
	setAppState(ApplicationState::PlanDive);

	plan(&test_deco_state, &testPlan, &displayed_dive, 60, stoptable, &cache, 1, 0);

#if DEBUG
	free(displayed_dive.notes);
	displayed_dive.notes = NULL;
	save_dive(stdout, &displayed_dive, false);
#endif

	gasmix gas;
	gas = get_gasmix_at_time(&displayed_dive, &displayed_dive.dc, {20 * 60 + 1});
	QCOMPARE(get_o2(gas), 110);
	QVERIFY(compareDecoTime(displayed_dive.dc.duration.seconds, 2480u, 2480u));
}
示例#7
0
void save_dives(const char *filename)
{
	int i;
	struct dive *dive;
	dive_trip_t *trip = NULL;

	FILE *f = g_fopen(filename, "w");

	if (!f)
		return;

	/* Flush any edits of current dives back to the dives! */
	update_dive(current_dive);

	fprintf(f, "<dives>\n<program name='subsurface' version='%d'></program>\n", VERSION);

	/* save the dives */
	for_each_dive(i, dive) {
		dive_trip_t *thistrip = dive->divetrip;
		if (trip != thistrip) {
			/* Close the old trip? */
			if (trip)
				fprintf(f, "</trip>\n");
			/* Open the new one */
			if (thistrip)
				save_trip(f, thistrip);
			trip = thistrip;
		}
		save_dive(f, get_dive(i));
	}
示例#8
0
void DivePlannerPointsModel::createTemporaryPlan()
{
	// Get the user-input and calculate the dive info
	free_dps(&diveplan);
	int lastIndex = -1;
	for (int i = 0; i < rowCount(); i++) {
		divedatapoint p = at(i);
		int deltaT = lastIndex != -1 ? p.time - at(lastIndex).time : p.time;
		lastIndex = i;
		if (i == 0 && prefs.drop_stone_mode) {
			/* Okay, we add a fist segment where we go down to depth */
			plan_add_segment(&diveplan, p.depth / prefs.descrate, p.depth, p.gasmix, p.setpoint, true);
			deltaT -= p.depth / prefs.descrate;
		}
		if (p.entered)
			plan_add_segment(&diveplan, deltaT, p.depth, p.gasmix, p.setpoint, true);
	}

	// what does the cache do???
	char *cache = NULL;
	struct divedatapoint *dp = NULL;
	for (int i = 0; i < MAX_CYLINDERS; i++) {
		cylinder_t *cyl = &displayed_dive.cylinder[i];
		if (cyl->depth.mm) {
			dp = create_dp(0, cyl->depth.mm, cyl->gasmix, 0);
			if (diveplan.dp) {
				dp->next = diveplan.dp;
				diveplan.dp = dp;
			} else {
				dp->next = NULL;
				diveplan.dp = dp;
			}
		}
	}
#if DEBUG_PLAN
	dump_plan(&diveplan);
#endif
	if (recalcQ() && !diveplan_empty(&diveplan)) {
		plan(&diveplan, &cache, isPlanner(), false);
		/* TODO:
		 * Hook this signal to the mainwindow(s), the call to MainWindow
		 * can't be here as we are now dealing with QML too.
		 */
		//MainWindow::instance()->setPlanNotes(displayed_dive.notes);
		emit calculatedPlanNotes(displayed_dive.notes);
	}
	// throw away the cache
	free(cache);
#if DEBUG_PLAN
	save_dive(stderr, &displayed_dive);
	dump_plan(&diveplan);
#endif
}
示例#9
0
void DivePlannerPointsModel::createTemporaryPlan()
{
	// Get the user-input and calculate the dive info
	free_dps(&diveplan);
	int lastIndex = -1;
	for (int i = 0; i < rowCount(); i++) {
		divedatapoint p = at(i);
		int deltaT = lastIndex != -1 ? p.time - at(lastIndex).time : p.time;
		lastIndex = i;
		if (i == 0 && mode == PLAN && prefs.drop_stone_mode) {
			/* Okay, we add a first segment where we go down to depth */
			plan_add_segment(&diveplan, p.depth.mm / prefs.descrate, p.depth.mm, p.cylinderid, p.setpoint, true);
			deltaT -= p.depth.mm / prefs.descrate;
		}
		if (p.entered)
			plan_add_segment(&diveplan, deltaT, p.depth.mm, p.cylinderid, p.setpoint, true);
	}

	// what does the cache do???
	struct deco_state *cache = NULL;
	struct divedatapoint *dp = NULL;
	for (int i = 0; i < MAX_CYLINDERS; i++) {
		cylinder_t *cyl = &displayed_dive.cylinder[i];
		if (cyl->depth.mm && cyl->cylinder_use != NOT_USED) {
			dp = create_dp(0, cyl->depth.mm, i, 0);
			if (diveplan.dp) {
				dp->next = diveplan.dp;
				diveplan.dp = dp;
			} else {
				dp->next = NULL;
				diveplan.dp = dp;
			}
		}
	}
#if DEBUG_PLAN
	dump_plan(&diveplan);
#endif
	if (recalcQ() && !diveplan_empty(&diveplan)) {
		struct decostop stoptable[60];
		plan(&diveplan, &displayed_dive, DECOTIMESTEP, stoptable, &cache, isPlanner(), false);
		computeVariations();
		emit calculatedPlanNotes();
	}
	// throw away the cache
	free(cache);
#if DEBUG_PLAN
	save_dive(stderr, &displayed_dive);
	dump_plan(&diveplan);
#endif
}
示例#10
0
void save_dives(const char *filename)
{
	int i;
	FILE *f = fopen(filename, "w");

	if (!f)
		return;

	/* Flush any edits of current dives back to the dives! */
	update_dive(current_dive);

	fprintf(f, "<dives>\n<program name='subsurface' version='%d'></program>\n", VERSION);
	for (i = 0; i < dive_table.nr; i++)
		save_dive(f, get_dive(i));
	fprintf(f, "</dives>\n");
	fclose(f);
}
示例#11
0
void TestPlan::testVpmbMetric100m10min()
{
	struct deco_state *cache = NULL;

	setupPrefsVpmb();
	prefs.unit_system = METRIC;
	prefs.units.length = units::METERS;

	struct diveplan testPlan = {};
	setupPlanVpmb100m10min(&testPlan);
	setAppState(ApplicationState::PlanDive);

	plan(&test_deco_state, &testPlan, &displayed_dive, 60, stoptable, &cache, 1, 0);

#if DEBUG
	free(displayed_dive.notes);
	displayed_dive.notes = NULL;
	save_dive(stdout, &displayed_dive, false);
#endif

	// check minimum gas result
	struct divedatapoint *dp = testPlan.dp;
	while (!dp->minimum_gas.mbar && dp->next)
		dp = dp->next;
	QCOMPARE(lrint(dp->minimum_gas.mbar / 1000.0), 175l);
	// print first ceiling
	printf("First ceiling %.1f m\n", (mbar_to_depth(test_deco_state.first_ceiling_pressure.mbar, &displayed_dive) * 0.001));
	// check first gas change to EAN50 at 21m
	struct event *ev = displayed_dive.dc.events;
	QVERIFY(ev != NULL);
	QCOMPARE(ev->gas.index, 1);
	QCOMPARE(ev->value, 50);
	QCOMPARE(get_depth_at_time(&displayed_dive.dc, ev->time.seconds), 21000);
	// check second gas change to Oxygen at 6m
	ev = ev->next;
	QVERIFY(ev != NULL);
	QCOMPARE(ev->gas.index, 2);
	QCOMPARE(ev->value, 100);
	QCOMPARE(get_depth_at_time(&displayed_dive.dc, ev->time.seconds), 6000);
	// check benchmark run time of 58 minutes, and known Subsurface runtime of 57 minutes
	QVERIFY(compareDecoTime(displayed_dive.dc.duration.seconds, 58u * 60u + 20u, 57u * 60u + 20u));
}
示例#12
0
void TestPlan::testImperial()
{
	struct deco_state *cache = NULL;

	setupPrefs();
	prefs.unit_system = IMPERIAL;
	prefs.units.length = units::FEET;
	prefs.planner_deco_mode = BUEHLMANN;

	struct diveplan testPlan = {};
	setupPlan(&testPlan);

	plan(&test_deco_state, &testPlan, &displayed_dive, 60, stoptable, &cache, 1, 0);

#if DEBUG
	free(displayed_dive.notes);
	displayed_dive.notes = NULL;
	save_dive(stdout, &displayed_dive, false);
#endif

	// check minimum gas result
	struct divedatapoint *dp = testPlan.dp;
	while (!dp->minimum_gas.mbar && dp->next)
		dp = dp->next;
	QCOMPARE(lrint(dp->minimum_gas.mbar / 1000.0), 154l);
	// check first gas change to EAN36 at 33m
	struct event *ev = displayed_dive.dc.events;
	QVERIFY(ev != NULL);
	QCOMPARE(ev->gas.index, 1);
	QCOMPARE(ev->value, 36);
	QCOMPARE(get_depth_at_time(&displayed_dive.dc, ev->time.seconds), 33528);
	// check second gas change to Oxygen at 6m
	ev = ev->next;
	QVERIFY(ev != NULL);
	QCOMPARE(ev->gas.index, 2);
	QCOMPARE(ev->value, 100);
	QCOMPARE(get_depth_at_time(&displayed_dive.dc, ev->time.seconds), 6096);
	// check expected run time of 111 minutes
	QVERIFY(compareDecoTime(displayed_dive.dc.duration.seconds, 111u * 60u - 2u, 111u * 60u - 2u));
}
示例#13
0
/* simply overwrite the data in the displayed_dive
 * return false if something goes wrong */
static void create_dive_from_plan(struct diveplan *diveplan, bool track_gas)
{
	struct divedatapoint *dp;
	struct divecomputer *dc;
	struct sample *sample;
	struct gasmix oldgasmix;
	struct event *ev;
	cylinder_t *cyl;
	int oldpo2 = 0;
	int lasttime = 0;
	int lastdepth = 0;

	if (!diveplan || !diveplan->dp)
		return;
#if DEBUG_PLAN & 4
	printf("in create_dive_from_plan\n");
	dump_plan(diveplan);
#endif
	// reset the cylinders and clear out the samples and events of the
	// displayed dive so we can restart
	reset_cylinders(&displayed_dive, track_gas);
	dc = &displayed_dive.dc;
	free(dc->sample);
	dc->sample = NULL;
	dc->samples = 0;
	dc->alloc_samples = 0;
	while ((ev = dc->events)) {
		dc->events = dc->events->next;
		free(ev);
	}
	dp = diveplan->dp;
	cyl = &displayed_dive.cylinder[0];
	oldgasmix = cyl->gasmix;
	sample = prepare_sample(dc);
	sample->po2.mbar = dp->po2;
	if (track_gas && cyl->type.workingpressure.mbar)
		sample->cylinderpressure.mbar = cyl->end.mbar;
	finish_sample(dc);
	while (dp) {
		struct gasmix gasmix = dp->gasmix;
		int po2 = dp->po2;
		int time = dp->time;
		int depth = dp->depth;

		if (time == 0) {
			/* special entries that just inform the algorithm about
			 * additional gases that are available */
			if (verify_gas_exists(gasmix) < 0)
				goto gas_error_exit;
			dp = dp->next;
			continue;
		}

		/* Check for SetPoint change */
		if (oldpo2 != po2) {
			if (lasttime)
				/* this is a bad idea - we should get a different SAMPLE_EVENT type
				 * reserved for this in libdivecomputer... overloading SMAPLE_EVENT_PO2
				 * with a different meaning will only cause confusion elsewhere in the code */
				add_event(dc, lasttime, SAMPLE_EVENT_PO2, 0, po2, "SP change");
			oldpo2 = po2;
		}

		/* Make sure we have the new gas, and create a gas change event */
		if (gasmix_distance(&gasmix, &oldgasmix) > 0) {
			int idx;
			if ((idx = verify_gas_exists(gasmix)) < 0)
				goto gas_error_exit;
			/* need to insert a first sample for the new gas */
			add_gas_switch_event(&displayed_dive, dc, lasttime + 1, idx);
			cyl = &displayed_dive.cylinder[idx];
			sample = prepare_sample(dc);
			sample[-1].po2.mbar = po2;
			sample->time.seconds = lasttime + 1;
			sample->depth.mm = lastdepth;
			if (track_gas && cyl->type.workingpressure.mbar)
				sample->cylinderpressure.mbar = cyl->sample_end.mbar;
			finish_sample(dc);
			oldgasmix = gasmix;
		}
		/* Create sample */
		sample = prepare_sample(dc);
		/* set po2 at beginning of this segment */
		/* and keep it valid for last sample - where it likely doesn't matter */
		sample[-1].po2.mbar = po2;
		sample->po2.mbar = po2;
		sample->time.seconds = lasttime = time;
		sample->depth.mm = lastdepth = depth;
		if (track_gas) {
			update_cylinder_pressure(&displayed_dive, sample[-1].depth.mm, depth, time - sample[-1].time.seconds,
					dp->entered ? diveplan->bottomsac : diveplan->decosac, cyl, !dp->entered);
			if (cyl->type.workingpressure.mbar)
				sample->cylinderpressure.mbar = cyl->end.mbar;
		}
		finish_sample(dc);
		dp = dp->next;
	}
#if DEBUG_PLAN & 32
	save_dive(stdout, &displayed_dive);
#endif
	return;

gas_error_exit:
	report_error(translate("gettextFromC", "Too many gas mixes"));
	return;
}
// TODO: This looks like should be ported to C code. or a big part of it.
bool DivelogsDeWebServices::prepare_dives_for_divelogs(const QString &tempfile, const bool selected)
{
	static const char errPrefix[] = "divelog.de-upload:";
	if (!amount_selected) {
		report_error(tr("no dives were selected").toUtf8());
		return false;
	}

	xsltStylesheetPtr xslt = NULL;
	struct zip *zip;

	xslt = get_stylesheet("divelogs-export.xslt");
	if (!xslt) {
		qDebug() << errPrefix << "missing stylesheet";
		return false;
	}


	int error_code;
	zip = zip_open(QFile::encodeName(tempfile), ZIP_CREATE, &error_code);
	if (!zip) {
		char buffer[1024];
		zip_error_to_str(buffer, sizeof buffer, error_code, errno);
		report_error(tr("failed to create zip file for upload: %s").toUtf8(), buffer);
		return false;
	}

	/* walk the dive list in chronological order */
	int i;
	struct dive *dive;
	for_each_dive (i, dive) {
		FILE *f;
		char filename[PATH_MAX];
		int streamsize;
		char *membuf;
		xmlDoc *transformed;
		struct zip_source *s;

		/*
		 * Get the i'th dive in XML format so we can process it.
		 * We need to save to a file before we can reload it back into memory...
		 */
		if (selected && !dive->selected)
			continue;
		f = tmpfile();
		if (!f) {
			report_error(tr("cannot create temporary file: %s").toUtf8(), qt_error_string().toUtf8().data());
			goto error_close_zip;
		}
		save_dive(f, dive);
		fseek(f, 0, SEEK_END);
		streamsize = ftell(f);
		rewind(f);

		membuf = (char *)malloc(streamsize + 1);
		if (!membuf || (streamsize = fread(membuf, 1, streamsize, f)) == 0) {
			report_error(tr("internal error: %s").toUtf8(), qt_error_string().toUtf8().data());
			fclose(f);
			free((void *)membuf);
			goto error_close_zip;
		}
		membuf[streamsize] = 0;
		fclose(f);

		/*
		 * Parse the memory buffer into XML document and
		 * transform it to divelogs.de format, finally dumping
		 * the XML into a character buffer.
		 */
		xmlDoc *doc = xmlReadMemory(membuf, streamsize, "divelog", NULL, 0);
		if (!doc) {
			qWarning() << errPrefix << "could not parse back into memory the XML file we've just created!";
			report_error(tr("internal error").toUtf8());
			free((void *)membuf);
			goto error_close_zip;
		}
		free((void *)membuf);

		transformed = xsltApplyStylesheet(xslt, doc, NULL);
		xmlDocDumpMemory(transformed, (xmlChar **)&membuf, &streamsize);
		xmlFreeDoc(doc);
		xmlFreeDoc(transformed);

		/*
		 * Save the XML document into a zip file.
		 */
		snprintf(filename, PATH_MAX, "%d.xml", i + 1);
		s = zip_source_buffer(zip, membuf, streamsize, 1);
		if (s) {
			int64_t ret = zip_add(zip, filename, s);
			if (ret == -1)
				qDebug() << errPrefix << "failed to include dive:" << i;
		}
	}
示例#15
0
/* This tests that a previously calculated plan isn't affecting the calculations of the next plan.
 * It is NOT a 'repetitive dive' test (i.e. with a surface interval and considering tissue
 * saturation from the previous dive).
 */
void TestPlan::testVpmbMetricRepeat()
{
	char *cache = NULL;

	setupPrefsVpmb();
	prefs.unit_system = METRIC;
	prefs.units.length = units::METERS;

	struct diveplan testPlan = { 0 };
	setupPlanVpmb30m20min(&testPlan);
	setCurrentAppState("PlanDive");

	plan(&testPlan, &cache, 1, 0);

#if DEBUG
	free(displayed_dive.notes);
	displayed_dive.notes = NULL;
	save_dive(stdout, &displayed_dive);
#endif

	// print first ceiling
	printf("First ceiling %.1f m\n", (mbar_to_depth(first_ceiling_pressure.mbar, &displayed_dive) * 0.001));
	// check benchmark run time of 27 minutes, and known Subsurface runtime of 27 minutes
	QVERIFY(compareDecoTime(displayed_dive.dc.duration.seconds, 27u * 60u + 20u, 27u * 60u + 20u));

	int firstDiveRunTimeSeconds = displayed_dive.dc.duration.seconds;

	setupPlanVpmb100mTo70m30min(&testPlan);
	plan(&testPlan, &cache, 1, 0);

#if DEBUG
	free(displayed_dive.notes);
	displayed_dive.notes = NULL;
	save_dive(stdout, &displayed_dive);
#endif

	// print first ceiling
	printf("First ceiling %.1f m\n", (mbar_to_depth(first_ceiling_pressure.mbar, &displayed_dive) * 0.001));
	// check first gas change to 21/35 at 66m
	struct event *ev = displayed_dive.dc.events;
	QVERIFY(ev != NULL);
	QCOMPARE(ev->gas.index, 1);
	QCOMPARE(ev->gas.mix.o2.permille, 210);
	QCOMPARE(ev->gas.mix.he.permille, 350);
	QCOMPARE(get_depth_at_time(&displayed_dive.dc, ev->time.seconds), 66000);
	// check second gas change to EAN50 at 21m
	ev = ev->next;
	QCOMPARE(ev->gas.index, 2);
	QCOMPARE(ev->value, 50);
	QCOMPARE(get_depth_at_time(&displayed_dive.dc, ev->time.seconds), 21000);
	// check third gas change to Oxygen at 6m
	ev = ev->next;
	QVERIFY(ev != NULL);
	QCOMPARE(ev->gas.index, 3);
	QCOMPARE(ev->value, 100);
	QCOMPARE(get_depth_at_time(&displayed_dive.dc, ev->time.seconds), 6000);
	// we don't have a benchmark, known Subsurface runtime is 126 minutes
	QVERIFY(compareDecoTime(displayed_dive.dc.duration.seconds, 126u * 60u + 20u, 126u * 60u + 20u));

	setupPlanVpmb30m20min(&testPlan);
	plan(&testPlan, &cache, 1, 0);

#if DEBUG
	free(displayed_dive.notes);
	displayed_dive.notes = NULL;
	save_dive(stdout, &displayed_dive);
#endif

	// print first ceiling
	printf("First ceiling %.1f m\n", (mbar_to_depth(first_ceiling_pressure.mbar, &displayed_dive) * 0.001));

	// check runtime is exactly the same as the first time
	int finalDiveRunTimeSeconds = displayed_dive.dc.duration.seconds;
	QCOMPARE(finalDiveRunTimeSeconds, firstDiveRunTimeSeconds);
}