1<?php
2
3/*
4	Phoronix Test Suite
5	URLs: http://www.phoronix.com, http://www.phoronix-test-suite.com/
6	Copyright (C) 2008 - 2021, Phoronix Media
7	Copyright (C) 2008 - 2021, Michael Larabel
8
9	This program is free software; you can redistribute it and/or modify
10	it under the terms of the GNU General Public License as published by
11	the Free Software Foundation; either version 3 of the License, or
12	(at your option) any later version.
13
14	This program is distributed in the hope that it will be useful,
15	but WITHOUT ANY WARRANTY; without even the implied warranty of
16	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17	GNU General Public License for more details.
18
19	You should have received a copy of the GNU General Public License
20	along with this program. If not, see <http://www.gnu.org/licenses/>.
21*/
22
23class pts_result_file
24{
25	protected $save_identifier = null;
26	protected $result_objects = null;
27	protected $extra_attributes = null;
28	protected $is_multi_way_inverted = false;
29	protected $file_location = false;
30
31	private $title = null;
32	private $description = null;
33	private $notes = null;
34	private $internal_tags = null;
35	private $reference_id = null;
36	private $preset_environment_variables = null;
37	public $systems = null;
38	private $is_tracker = -1;
39	private $last_modified = null;
40	private $ro_relation_map = null;
41
42	public function __construct($result_file = null, $read_only_result_objects = false, $parse_only_qualified_result_objects = false)
43	{
44		$this->save_identifier = $result_file;
45		$this->extra_attributes = array();
46		$this->systems = array();
47		$this->result_objects = array();
48		$this->ro_relation_map = array();
49
50		if($result_file == null)
51		{
52			return;
53		}
54		else if(is_file($result_file))
55		{
56			$this->file_location = $result_file;
57			$result_file = file_get_contents($result_file);
58		}
59		else if(!isset($result_file[1024]) && defined('PTS_SAVE_RESULTS_PATH') && is_file(PTS_SAVE_RESULTS_PATH . $result_file . '/composite.xml'))
60		{
61			$this->file_location = PTS_SAVE_RESULTS_PATH . $result_file . '/composite.xml';
62			$result_file = file_get_contents($this->file_location);
63		}
64
65		$xml = simplexml_load_string($result_file, 'SimpleXMLElement', LIBXML_COMPACT | LIBXML_PARSEHUGE);
66		if(isset($xml->Generated))
67		{
68			$this->title = self::clean_input($xml->Generated->Title);
69			$this->description = self::clean_input($xml->Generated->Description);
70			$this->notes = self::clean_input($xml->Generated->Notes);
71			$this->internal_tags = self::clean_input($xml->Generated->InternalTags);
72			$this->reference_id = self::clean_input($xml->Generated->ReferenceID);
73			$this->preset_environment_variables = self::clean_input($xml->Generated->PreSetEnvironmentVariables);
74			$this->last_modified = $xml->Generated->LastModified;
75		}
76
77		if(isset($xml->System))
78		{
79			foreach($xml->System as $s)
80			{
81				$this->systems[] = new pts_result_file_system(self::clean_input($s->Identifier->__toString()), self::clean_input($s->Hardware->__toString()), self::clean_input($s->Software->__toString()), json_decode(self::clean_input($s->JSON), true), self::clean_input($s->User->__toString()), self::clean_input($s->Notes->__toString()), self::clean_input($s->TimeStamp->__toString()), self::clean_input($s->ClientVersion->__toString()), $this);
82			}
83		}
84
85		if(isset($xml->Result))
86		{
87			foreach($xml->Result as $result)
88			{
89				if($parse_only_qualified_result_objects && ($result->Identifier == null || $result->Identifier->__toString() == null))
90				{
91					continue;
92				}
93
94				$test_profile = new pts_test_profile(($result->Identifier != null ? $result->Identifier->__toString() : null), null, !$read_only_result_objects);
95				$test_profile->set_test_title($result->Title->__toString());
96				$test_profile->set_version($result->AppVersion->__toString());
97				$test_profile->set_result_scale($result->Scale->__toString());
98				$test_profile->set_result_proportion($result->Proportion->__toString());
99				$test_profile->set_display_format($result->DisplayFormat->__toString());
100
101				$test_result = new pts_test_result($test_profile);
102				$test_result->set_used_arguments_description($result->Description->__toString());
103				$test_result->set_used_arguments($result->Arguments->__toString());
104				$test_result->set_annotation((isset($result->Annotation) ? $result->Annotation->__toString() : null));
105				$parent = (isset($result->Parent) ? $result->Parent->__toString() : null);
106				$test_result->set_parent_hash($parent);
107
108				$result_buffer = new pts_test_result_buffer();
109				foreach($result->Data->Entry as $entry)
110				{
111					$result_buffer->add_test_result($entry->Identifier->__toString(), $entry->Value->__toString(), $entry->RawString->__toString(), (isset($entry->JSON) ? $entry->JSON->__toString() : null));
112				}
113				$test_result->set_test_result_buffer($result_buffer);
114				$this_ch = $test_result->get_comparison_hash(true, false);
115				$this->result_objects[$this_ch] = $test_result;
116
117				if($parent)
118				{
119					if(!isset($this->ro_relation_map[$parent]))
120					{
121						$this->ro_relation_map[$parent] = array();
122					}
123					$this->ro_relation_map[$parent][] = $this_ch;
124				}
125			}
126		}
127
128		unset($xml);
129	}
130	public function __clone()
131	{
132		foreach($this->result_objects as $i => $v)
133		{
134			$this->result_objects[$i] = clone $this->result_objects[$i];
135		}
136	}
137	public function get_relation_map($parent = null)
138	{
139		if($parent)
140		{
141			return isset($this->ro_relation_map[$parent]) ? $this->ro_relation_map[$parent] : array();
142		}
143		else
144		{
145			return $this->ro_relation_map;
146		}
147	}
148	public function get_file_location()
149	{
150		if($this->file_location)
151		{
152			return $this->file_location;
153		}
154		else if($this->save_identifier)
155		{
156			return PTS_SAVE_RESULTS_PATH . $this->save_identifier . '/composite.xml';
157		}
158	}
159	public function get_result_dir()
160	{
161		$composite_xml_dir = dirname($this->get_file_location());
162		return empty($composite_xml_dir) || !is_dir($composite_xml_dir) ? false : $composite_xml_dir . '/';
163	}
164	public function get_system_log_dir($result_identifier = null, $dir_check = true)
165	{
166		$log_dir = dirname($this->get_file_location());
167		if(empty($log_dir) || !is_dir($log_dir))
168		{
169			return false;
170		}
171
172		$sdir = $log_dir . '/system-logs/';
173
174		if($result_identifier == null)
175		{
176			return $sdir;
177		}
178		else
179		{
180			$sdir = $sdir . pts_strings::simplify_string_for_file_handling($result_identifier) . '/';
181			return !$dir_check || is_dir($sdir) ? $sdir : false;
182		}
183	}
184	public function get_test_log_dir(&$result_object = null)
185	{
186		$log_dir = dirname($this->get_file_location());
187		if(empty($log_dir) || !is_dir($log_dir))
188		{
189			return false;
190		}
191
192		return $log_dir . '/test-logs/' . ($result_object != null ? $result_object->get_comparison_hash(true, false) . '/' : null);
193	}
194	public function get_test_installation_log_dir()
195	{
196		$log_dir = dirname($this->get_file_location());
197		if(empty($log_dir) || !is_dir($log_dir))
198		{
199			return false;
200		}
201
202		return $log_dir . '/installation-logs/';
203	}
204	public function save()
205	{
206		if($this->get_file_location() && is_file($this->get_file_location()))
207		{
208			return file_put_contents($this->get_file_location(), $this->get_xml());
209		}
210	}
211	public function get_last_modified()
212	{
213		return $this->last_modified;
214	}
215	public function validate()
216	{
217		$dom = new DOMDocument();
218		$dom->loadXML($this->get_xml());
219		return $dom->schemaValidate(pts_openbenchmarking::openbenchmarking_standards_path() . 'schemas/result-file.xsd');
220	}
221	public function __toString()
222	{
223		return $this->get_identifier();
224	}
225	protected static function clean_input($value)
226	{
227		return strip_tags($value);
228		/*
229		if(is_array($value))
230		{
231			return array_map(array($this, 'clean_input'), $value);
232		}
233		else
234		{
235			return strip_tags($value);
236		}
237		*/
238	}
239	public function default_result_folder_path()
240	{
241		return PTS_SAVE_RESULTS_PATH . $this->save_identifier . '/';
242	}
243	public function get_identifier()
244	{
245		return $this->save_identifier;
246	}
247	public function read_extra_attribute($key)
248	{
249		return isset($this->extra_attributes[$key]) ? $this->extra_attributes[$key] : false;
250	}
251	public function set_extra_attribute($key, $value)
252	{
253		$this->extra_attributes[$key] = $value;
254	}
255	public function add_system($system)
256	{
257		if(!in_array($system, $this->systems))
258		{
259			$this->systems[] = $system;
260		}
261	}
262	public function add_system_direct($identifier, $hw = null, $sw = null, $json = null, $user = null, $notes = null, $timestamp = null, $version = null)
263	{
264		$this->systems[] = new pts_result_file_system($identifier, $hw, $sw, $json, $user, $notes, $timestamp, $version, $this);
265	}
266	public function get_systems()
267	{
268		return $this->systems;
269	}
270	public function get_system_hardware()
271	{
272		// XXX this is deprecated
273		$hw = array();
274		foreach($this->systems as &$s)
275		{
276			$hw[] = $s->get_hardware();
277		}
278		return $hw;
279	}
280	public function get_system_software()
281	{
282		// XXX this is deprecated
283		$sw = array();
284		foreach($this->systems as &$s)
285		{
286			$sw[] = $s->get_software();
287		}
288		return $sw;
289	}
290	public function get_system_identifiers()
291	{
292		// XXX this is deprecated
293		$ids = array();
294		foreach($this->systems as &$s)
295		{
296			$ids[] = $s->get_identifier();
297		}
298		return $ids;
299	}
300	public function is_system_identifier_in_result_file($identifier)
301	{
302		foreach($this->systems as &$s)
303		{
304			if($s->get_identifier() == $identifier)
305			{
306				return true;
307			}
308		}
309
310		return false;
311	}
312	public function system_logs_available()
313	{
314		foreach($this->systems as &$s)
315		{
316			if($s->has_log_files())
317			{
318				return true;
319			}
320		}
321
322		return false;
323	}
324	public function get_system_count()
325	{
326		return count($this->systems);
327	}
328	public function set_title($new_title)
329	{
330		if($new_title != null)
331		{
332			$this->title = $new_title;
333		}
334	}
335	public function get_title()
336	{
337		return $this->title;
338	}
339	public function append_description($append_description)
340	{
341		if($append_description != null && strpos($this->description, $append_description) === false)
342		{
343			$this->description .= PHP_EOL . $append_description;
344		}
345	}
346	public function set_description($new_description)
347	{
348		if($new_description != null)
349		{
350			$this->description = $new_description;
351		}
352	}
353	public function get_description()
354	{
355		return $this->description;
356	}
357	public function set_notes($notes)
358	{
359		if($notes != null)
360		{
361			$this->notes = $notes;
362		}
363	}
364	public function get_notes()
365	{
366		return $this->notes;
367	}
368	public function set_internal_tags($tags)
369	{
370		if($tags != null)
371		{
372			$this->internal_tags = $tags;
373		}
374	}
375	public function get_internal_tags()
376	{
377		return $this->internal_tags;
378	}
379	public function set_reference_id($new_reference_id)
380	{
381		if($new_reference_id != null)
382		{
383			$this->reference_id = $new_reference_id;
384		}
385	}
386	public function get_reference_id()
387	{
388		return $this->reference_id;
389	}
390	public function set_preset_environment_variables($env)
391	{
392		if($env != null)
393		{
394			$this->preset_environment_variables = $env;
395		}
396	}
397	public function get_preset_environment_variables()
398	{
399		return $this->preset_environment_variables;
400	}
401	public function get_test_count()
402	{
403		return count($this->get_result_objects());
404	}
405	public function get_qualified_test_count()
406	{
407		$q_count = 0;
408		foreach($this->get_result_objects() as $ro)
409		{
410			if($ro->test_profile->get_identifier() != null)
411			{
412				$q_count++;
413			}
414		}
415		return $q_count;
416	}
417	public function has_matching_test_and_run_identifier(&$test_result, $run_identifier_to_check)
418	{
419		$found_match = false;
420		$hash_to_check = $test_result->get_comparison_hash();
421
422		foreach($this->get_result_objects() as $result_object)
423		{
424			if($hash_to_check == $result_object->get_comparison_hash())
425			{
426				if(in_array($run_identifier_to_check, $result_object->test_result_buffer->get_identifiers()) && $result_object->test_result_buffer->get_result_from_identifier($run_identifier_to_check) != '')
427				{
428					$found_match = true;
429				}
430				break;
431			}
432		}
433
434		return $found_match;
435	}
436	public function get_contained_tests_hash($raw_output = true)
437	{
438		$result_object_hashes = $this->get_result_object_hashes();
439		sort($result_object_hashes);
440		return sha1(implode(',', $result_object_hashes), $raw_output);
441	}
442	public function get_result_object_hashes()
443	{
444		$object_hashes = array();
445
446		foreach($this->get_result_objects() as $result_object)
447		{
448			$object_hashes[] = $result_object->get_comparison_hash();
449		}
450
451		return $object_hashes;
452	}
453	public function is_results_tracker()
454	{
455		// If there are more than five results and the only changes in the system identifier names are numeric changes, assume it's a tracker
456		// i.e. different dates or different versions of a package being tested
457		if($this->is_tracker === -1)
458		{
459			$identifiers = $this->get_system_identifiers();
460
461			if(isset($identifiers[5]))
462			{
463				// dirty SHA1 hash check
464				$is_sha1_hash = strlen($identifiers[0]) == 40 && strpos($identifiers[0], ' ') === false;
465				$has_sha1_shorthash = false;
466
467				foreach($identifiers as $i => &$identifier)
468				{
469					$has_sha1_shorthash = ($i == 0 || $has_sha1_shorthash) && isset($identifier[7]) && pts_strings::string_only_contains(substr($identifier, -8), pts_strings::CHAR_NUMERIC | pts_strings::CHAR_LETTER) && strpos($identifier, ' ') === false;
470					$identifier = pts_strings::remove_from_string($identifier, pts_strings::CHAR_NUMERIC | pts_strings::CHAR_DASH | pts_strings::CHAR_DECIMAL);
471				}
472
473				$this->is_tracker = count(array_unique($identifiers)) <= 1 || $is_sha1_hash || $has_sha1_shorthash;
474
475				if($this->is_tracker)
476				{
477					$hw = $this->get_system_hardware();
478
479					if(isset($hw[1]) && count($hw) == count(array_unique($hw)))
480					{
481						// it can't be a results tracker if the hardware is always different
482						$this->is_tracker = false;
483					}
484				}
485
486				if($this->is_tracker == false)
487				{
488					// See if only numbers are changing between runs
489					foreach($identifiers as $i => &$identifier)
490					{
491						if(($x = strpos($identifier, ': ')) !== false)
492						{
493							$identifier = substr($identifier, ($x + 2));
494						}
495						if($i > 0 && pts_strings::remove_from_string($identifier, pts_strings::CHAR_NUMERIC | pts_strings::CHAR_DECIMAL) != pts_strings::remove_from_string($identifiers[($i - 1)], pts_strings::CHAR_NUMERIC | pts_strings::CHAR_DECIMAL))
496						{
497							return false;
498						}
499					}
500					$this->is_tracker = true;
501				}
502			}
503			else
504			{
505				// Definitely not a tracker as not over 5 results
506				$this->is_tracker = false;
507			}
508		}
509
510		return $this->is_tracker;
511	}
512	public function is_multi_way_comparison($identifiers = false, $extra_attributes = null)
513	{
514		if(isset($extra_attributes['force_tracking_line_graph']))
515		{
516			// Phoromatic result tracker
517			$is_multi_way = true;
518			$this->is_multi_way_inverted = true;
519		}
520		else
521		{
522			$hw = null; // XXX: this isn't used anymore at least for now on system hardware
523			if($identifiers == false)
524			{
525				$identifiers = $this->get_system_identifiers();
526			}
527			$is_multi_way = count($identifiers) < 2 ? false : pts_render::multi_way_identifier_check($identifiers, $hw, $this);
528			$this->is_multi_way_inverted = $is_multi_way && $is_multi_way[1];
529		}
530
531		return $is_multi_way;
532	}
533	public function invert_multi_way_invert()
534	{
535		$this->is_multi_way_inverted = !$this->is_multi_way_inverted;
536	}
537	public function is_multi_way_inverted()
538	{
539		return $this->is_multi_way_inverted;
540	}
541	public function get_contained_test_profiles($unique = false)
542	{
543		$test_profiles = array();
544
545		foreach($this->get_result_objects() as $object)
546		{
547			$test_profiles[] = $object->test_profile;
548		}
549		if($unique)
550		{
551			$test_profiles = array_unique($test_profiles);
552		}
553
554		return $test_profiles;
555	}
556	public function override_result_objects($result_objects)
557	{
558		$this->result_objects = $result_objects;
559	}
560	public function get_result($ch)
561	{
562		return isset($this->result_objects[$ch]) ? $this->result_objects[$ch] : false;
563	}
564	public function remove_result_object_by_id($index_or_indexes, $delete_child_objects = true)
565	{
566		$did_remove = false;
567		foreach(pts_arrays::to_array($index_or_indexes) as $index)
568		{
569			if(isset($this->result_objects[$index]))
570			{
571				unset($this->result_objects[$index]);
572				$did_remove = true;
573
574				if($delete_child_objects)
575				{
576					foreach($this->get_relation_map($index) as $child_ro)
577					{
578						if(isset($this->result_objects[$child_ro]))
579						{
580							unset($this->result_objects[$child_ro]);
581						}
582					}
583				}
584			}
585		}
586		return $did_remove;
587	}
588	public function remove_noisy_results($noise_level_percent = 6)
589	{
590		foreach($this->result_objects as $i => &$ro)
591		{
592			if($ro->has_noisy_result($noise_level_percent))
593			{
594				$this->remove_result_object_by_id($i);
595			}
596		}
597	}
598	public function reduce_precision()
599	{
600		foreach($this->result_objects as $i => &$ro)
601		{
602			$ro->test_result_buffer->reduce_precision();
603		}
604	}
605	public function update_annotation_for_result_object_by_id($index, $annotation)
606	{
607		if(isset($this->result_objects[$index]))
608		{
609			$this->result_objects[$index]->set_annotation($annotation);
610			return true;
611		}
612		return false;
613	}
614	public function get_result_object_by_hash($h)
615	{
616		return isset($this->result_objects[$h]) ? $this->result_objects[$h] : false;
617	}
618	public function get_result_objects($select_indexes = -1)
619	{
620		if($select_indexes != -1 && $select_indexes !== null)
621		{
622			$objects = array();
623
624			if($select_indexes == 'ONLY_CHANGED_RESULTS')
625			{
626				foreach($this->result_objects as &$result)
627				{
628					// Only show results where the variation was greater than or equal to 1%
629					if(abs($result->largest_result_variation(0.01)) >= 0.01)
630					{
631						$objects[] = $result;
632					}
633				}
634			}
635			else
636			{
637				foreach(pts_arrays::to_array($select_indexes) as $index)
638				{
639					if(isset($this->result_objects[$index]))
640					{
641						$objects[] = $this->result_objects[$index];
642					}
643				}
644			}
645
646			return $objects;
647		}
648
649		$skip_objects = defined('SKIP_RESULT_OBJECTS') ? explode(',', SKIP_RESULT_OBJECTS) : false;
650		if($skip_objects)
651		{
652			$ros = $this->result_objects;
653			foreach($ros as $index => $ro)
654			{
655				foreach($skip_objects as $skip)
656				{
657					if(stripos($ro->test_profile->get_identifier(), $skip) !== false || stripos($ro->get_arguments_description(), $skip) !== false)
658					{
659						unset($ros[$index]);
660						break;
661					}
662				}
663			}
664
665			return $ros;
666		}
667
668		return $this->result_objects;
669	}
670	public function to_json()
671	{
672		$file = $this->get_xml();
673		$file = str_replace(array("\n", "\r", "\t"), '', $file);
674		$file = trim(str_replace('"', "'", $file));
675		$simple_xml = simplexml_load_string($file);
676		return json_encode($simple_xml);
677	}
678	public function avoid_duplicate_identifiers()
679	{
680		// avoid duplicate test identifiers
681		$identifiers = $this->get_system_identifiers();
682		if(count($identifiers) < 2)
683		{
684			return;
685		}
686		foreach(pts_arrays::duplicates_in_array($identifiers) as $duplicate)
687		{
688			while($this->is_system_identifier_in_result_file($duplicate))
689			{
690				$i = 0;
691				do
692				{
693					$i++;
694					$new_identifier = $duplicate . ' #' . $i;
695				}
696				while($this->is_system_identifier_in_result_file($new_identifier));
697				$this->rename_run($duplicate, $new_identifier, false);
698			}
699		}
700	}
701	public function rename_run($from, $to, $rename_logs = true)
702	{
703		if($from == 'PREFIX')
704		{
705			foreach($this->systems as &$s)
706			{
707				$s->set_identifier($to . ': ' . $s->get_identifier());
708			}
709		}
710		else if($from == null)
711		{
712			if(count($this->systems) == 1)
713			{
714				foreach($this->systems as &$s)
715				{
716					$s->set_identifier($to);
717					break;
718				}
719			}
720		}
721		else
722		{
723			$found = false;
724			foreach($this->systems as &$s)
725			{
726				if($s->get_identifier() == $from)
727				{
728					$found = true;
729					$s->set_identifier($to);
730					break;
731				}
732			}
733			if($found && $rename_logs && ($d = $this->get_system_log_dir($from, true)))
734			{
735				$d = dirname(dirname($d)) . '/';
736
737				foreach(array('test-logs', 'system-logs', 'installation-logs') as $dir_name)
738				{
739					if(is_dir($d . $dir_name . '/' . $from))
740					{
741						rename($d . $dir_name . '/' . $from, $d . $dir_name . '/' . $to);
742					}
743				}
744			}
745		}
746
747		foreach($this->result_objects as &$result)
748		{
749			$result->test_result_buffer->rename($from, $to);
750		}
751	}
752	public function reorder_runs($new_order)
753	{
754		foreach($new_order as $identifier)
755		{
756			foreach($this->systems as $i => $s)
757			{
758				if($s->get_identifier() == $identifier)
759				{
760					$c = $s;
761					unset($this->systems[$i]);
762					$this->systems[] = $c;
763					break;
764				}
765			}
766		}
767
768		foreach($this->result_objects as &$result)
769		{
770			$result->test_result_buffer->reorder($new_order);
771		}
772	}
773	public function remove_run($remove)
774	{
775		$remove = pts_arrays::to_array($remove);
776		foreach($this->systems as $i => &$s)
777		{
778			if(in_array($s->get_identifier(), $remove))
779			{
780				unset($this->systems[$i]);
781			}
782		}
783
784		foreach($this->result_objects as &$result)
785		{
786			$result->test_result_buffer->remove($remove);
787		}
788	}
789	public function add_to_result_file(&$result_file, $only_merge_results_already_present = false)
790	{
791		foreach($result_file->get_systems() as $s)
792		{
793			if(!in_array($s, $this->systems))
794			{
795				$this->systems[] = $s;
796			}
797		}
798
799		foreach($result_file->get_result_objects() as $result)
800		{
801			$this->add_result($result, $only_merge_results_already_present);
802		}
803	}
804	public function result_hash_exists(&$result_object)
805	{
806		$ch = $result_object->get_comparison_hash(true, false);
807		return isset($this->result_objects[$ch]) && isset($this->result_objects[$ch]->test_result_buffer);
808	}
809	public function add_result(&$result_object, $only_if_result_already_present = false)
810	{
811		if($result_object == null)
812		{
813			return false;
814		}
815
816		$ch = $result_object->get_comparison_hash(true, false);
817		if(isset($this->result_objects[$ch]) && isset($this->result_objects[$ch]->test_result_buffer))
818		{
819			if($result_object->get_annotation() != null)
820			{
821				$this->result_objects[$ch]->append_annotation($result_object->get_annotation());
822			}
823			foreach($result_object->test_result_buffer->get_buffer_items() as $bi)
824			{
825				if($bi->get_result_value() === null)
826				{
827					continue;
828				}
829
830				$this->result_objects[$ch]->test_result_buffer->add_buffer_item($bi);
831			}
832		}
833		else if($only_if_result_already_present == false)
834		{
835			$this->result_objects[$ch] = $result_object;
836		}
837
838		$parent = $result_object->get_parent_hash();
839		if($parent)
840		{
841			if(!isset($this->ro_relation_map[$parent]))
842			{
843				$this->ro_relation_map[$parent] = array();
844			}
845			$this->ro_relation_map[$parent][] = $ch;
846		}
847
848		return $ch;
849	}
850	public function add_result_return_object(&$result_object, $only_if_result_already_present = false)
851	{
852		$ch = $this->add_result($result_object, $only_if_result_already_present);
853		return isset($this->result_objects[$ch]) ? $this->result_objects[$ch] : false;
854	}
855	public function get_xml($to = null, $force_nice_formatting = false)
856	{
857		$xml_writer = new nye_XmlWriter(null, $force_nice_formatting);
858		$xml_writer->addXmlNode('PhoronixTestSuite/Generated/Title', $this->get_title());
859		$xml_writer->addXmlNode('PhoronixTestSuite/Generated/LastModified', date('Y-m-d H:i:s', pts_client::current_time()));
860		$xml_writer->addXmlNode('PhoronixTestSuite/Generated/TestClient', pts_core::program_title(true));
861		$xml_writer->addXmlNode('PhoronixTestSuite/Generated/Description', $this->get_description());
862		$xml_writer->addXmlNodeWNE('PhoronixTestSuite/Generated/Notes', $this->get_notes());
863		$xml_writer->addXmlNodeWNE('PhoronixTestSuite/Generated/InternalTags', $this->get_internal_tags());
864		$xml_writer->addXmlNodeWNE('PhoronixTestSuite/Generated/ReferenceID', $this->get_reference_id());
865		$xml_writer->addXmlNodeWNE('PhoronixTestSuite/Generated/PreSetEnvironmentVariables', $this->get_preset_environment_variables());
866
867		// Write the system hardware/software information
868		foreach($this->get_systems() as $s)
869		{
870			$xml_writer->addXmlNode('PhoronixTestSuite/System/Identifier', $s->get_identifier());
871			$xml_writer->addXmlNode('PhoronixTestSuite/System/Hardware', $s->get_hardware());
872			$xml_writer->addXmlNode('PhoronixTestSuite/System/Software', $s->get_software());
873			$xml_writer->addXmlNode('PhoronixTestSuite/System/User', $s->get_username());
874			$xml_writer->addXmlNode('PhoronixTestSuite/System/TimeStamp', $s->get_timestamp());
875			$xml_writer->addXmlNode('PhoronixTestSuite/System/TestClientVersion', $s->get_client_version());
876			$xml_writer->addXmlNode('PhoronixTestSuite/System/Notes', $s->get_notes());
877
878			if(!defined('USER_PTS_CORE_VERSION') || USER_PTS_CORE_VERSION > 3722)
879			{
880				// Ensure that a supported result file schema is being written...
881				// USER_PTS_CORE_VERSION is set by OpenBenchmarking.org so if the requested client is old, don't write this data to send back to their version
882				$xml_writer->addXmlNodeWNE('PhoronixTestSuite/System/JSON', ($s->get_json() ? json_encode($s->get_json()) : null));
883			}
884		}
885
886		// Write the results
887		foreach($this->get_result_objects() as $result_object)
888		{
889			$buffer_items = $result_object->test_result_buffer->get_buffer_items();
890
891			if(count($buffer_items) == 0)
892			{
893				continue;
894			}
895
896			$xml_writer->addXmlNode('PhoronixTestSuite/Result/Identifier', $result_object->test_profile->get_identifier());
897			$xml_writer->addXmlNode('PhoronixTestSuite/Result/Title', $result_object->test_profile->get_title());
898			$xml_writer->addXmlNode('PhoronixTestSuite/Result/AppVersion', $result_object->test_profile->get_app_version());
899			$xml_writer->addXmlNode('PhoronixTestSuite/Result/Arguments', $result_object->get_arguments());
900			$xml_writer->addXmlNode('PhoronixTestSuite/Result/Description', $result_object->get_arguments_description());
901			$xml_writer->addXmlNode('PhoronixTestSuite/Result/Scale', $result_object->test_profile->get_result_scale());
902			$xml_writer->addXmlNode('PhoronixTestSuite/Result/Proportion', $result_object->test_profile->get_result_proportion());
903			$xml_writer->addXmlNode('PhoronixTestSuite/Result/DisplayFormat', $result_object->test_profile->get_display_format());
904			$xml_writer->addXmlNodeWNE('PhoronixTestSuite/Result/Annotation', $result_object->get_annotation());
905			$xml_writer->addXmlNodeWNE('PhoronixTestSuite/Result/Parent', $result_object->get_parent_hash());
906
907			foreach($buffer_items as $i => &$buffer_item)
908			{
909				$xml_writer->addXmlNode('PhoronixTestSuite/Result/Data/Entry/Identifier', $buffer_item->get_result_identifier());
910				$xml_writer->addXmlNode('PhoronixTestSuite/Result/Data/Entry/Value', $buffer_item->get_result_value());
911				$xml_writer->addXmlNode('PhoronixTestSuite/Result/Data/Entry/RawString', $buffer_item->get_result_raw());
912
913				if(!defined('USER_PTS_CORE_VERSION') || USER_PTS_CORE_VERSION > 3722)
914				{
915					// Ensure that a supported result file schema is being written...
916					// USER_PTS_CORE_VERSION is set by OpenBenchmarking.org so if the requested client is old, don't write this data to send back to their version
917					$xml_writer->addXmlNodeWNE('PhoronixTestSuite/Result/Data/Entry/JSON', ($buffer_item->get_result_json() ? json_encode($buffer_item->get_result_json()) : null));
918				}
919			}
920		}
921
922		return $to == null ? $xml_writer->getXML() : $xml_writer->saveXMLFile($to);
923	}
924	public function merge($result_merges_to_combine, $pass_attributes = 0, $add_prefix = null, $merge_meta = false, $only_prefix_on_collision = false)
925	{
926		if(!is_array($result_merges_to_combine) || empty($result_merges_to_combine))
927		{
928			return false;
929		}
930
931		foreach($result_merges_to_combine as $i => &$merge_select)
932		{
933			if(!($merge_select instanceof $merge_select))
934			{
935				$merge_select = new pts_result_merge_select($merge_select);
936			}
937
938			if(!is_file($merge_select->get_result_file()) && !($merge_select->get_result_file() instanceof pts_result_file))
939			{
940				if(defined('PTS_SAVE_RESULTS_PATH') && is_file(PTS_SAVE_RESULTS_PATH . $merge_select->get_result_file() . '/composite.xml'))
941				{
942					$merge_select->set_result_file(PTS_SAVE_RESULTS_PATH . $merge_select->get_result_file() . '/composite.xml');
943				}
944				else
945				{
946					unset($result_merges_to_combine[$i]);
947				}
948			}
949		}
950
951		if(empty($result_merges_to_combine))
952		{
953			return false;
954		}
955
956		foreach($result_merges_to_combine as &$merge_select)
957		{
958			if($merge_select->get_result_file() instanceof pts_result_file)
959			{
960				$result_file = $merge_select->get_result_file();
961			}
962			else
963			{
964				$result_file = new pts_result_file($merge_select->get_result_file(), true);
965			}
966
967			if($add_prefix)
968			{
969				if($only_prefix_on_collision)
970				{
971					$this_identifiers = $this->get_system_identifiers();
972					foreach($result_file->systems as &$s)
973					{
974						if(in_array($s->get_identifier(), $this_identifiers))
975						{
976							$s->set_identifier($add_prefix . ': ' . $s->get_identifier());
977						}
978					}
979				}
980				else
981				{
982					$result_file->rename_run('PREFIX', $add_prefix);
983				}
984			}
985			else if($merge_select->get_rename_identifier())
986			{
987				$result_file->rename_run(null, $merge_select->get_rename_identifier());
988			}
989
990			if($this->get_title() == null && $result_file->get_title() != null)
991			{
992				$this->set_title($result_file->get_title());
993			}
994
995			if($this->get_description() == null && $result_file->get_description() != null)
996			{
997				$this->set_description($result_file->get_description());
998			}
999
1000			$this->add_to_result_file($result_file);
1001
1002			if($merge_meta)
1003			{
1004				if($result_file->get_title() != null && stripos($this->get_title(), $result_file->get_title()) === false)
1005				{
1006					$this->set_title($this->get_title() . ', ' . $result_file->get_title());
1007				}
1008				if($result_file->get_description() != null && stripos($this->get_description(), $result_file->get_description()) === false)
1009				{
1010					$this->set_description($this->get_description() . PHP_EOL . PHP_EOL . $result_file->get_title() . ': ' . $result_file->get_description());
1011				}
1012			}
1013			unset($result_file);
1014		}
1015	}
1016	public function contains_system_hardware($search)
1017	{
1018		foreach($this->get_system_hardware() as $h)
1019		{
1020			if(stripos($h, $search) !== false)
1021			{
1022				return true;
1023			}
1024		}
1025		return false;
1026	}
1027	public function contains_system_software($search)
1028	{
1029		foreach($this->get_system_software() as $s)
1030		{
1031			if(stripos($s, $search) !== false)
1032			{
1033				return true;
1034			}
1035		}
1036		return false;
1037	}
1038	public function contains_test($search)
1039	{
1040		foreach($this->get_contained_test_profiles() as $test_profile)
1041		{
1042			if(stripos($test_profile->get_identifier(), $search) !== false || stripos($test_profile->get_title(), $search) !== false)
1043			{
1044				return true;
1045			}
1046		}
1047		return false;
1048	}
1049	public function sort_result_object_order_by_spread($asc = false)
1050	{
1051		uasort($this->result_objects, array('pts_result_file', 'result_spread_comparison'));
1052
1053		if($asc == false)
1054		{
1055			$this->result_objects = array_reverse($this->result_objects, true);
1056		}
1057	}
1058	public static function result_spread_comparison($a, $b)
1059	{
1060		return strcmp($a->get_spread(), $b->get_spread());
1061	}
1062	public function sort_result_object_order_by_title($asc = true)
1063	{
1064		uasort($this->result_objects, array('pts_result_file', 'result_title_comparison'));
1065
1066		if($asc == false)
1067		{
1068			$this->result_objects = array_reverse($this->result_objects, true);
1069		}
1070	}
1071	public static function result_title_comparison($a, $b)
1072	{
1073		return strcmp(strtolower($a->test_profile->get_title()) . ' ' . $a->test_profile->get_app_version(), strtolower($b->test_profile->get_title()) . ' ' . $b->test_profile->get_app_version());
1074	}
1075	public function sort_result_object_order_by_result_scale($asc = true)
1076	{
1077		uasort($this->result_objects, array('pts_result_file', 'result_scale_comparison'));
1078
1079		if($asc == false)
1080		{
1081			$this->result_objects = array_reverse($this->result_objects, true);
1082		}
1083	}
1084	public static function result_scale_comparison($a, $b)
1085	{
1086		return strcmp($a->test_profile->get_result_proportion() . ' ' . strtolower($a->test_profile->get_result_scale()) . ' ' . $a->test_profile->get_identifier(), $b->test_profile->get_result_proportion() . ' ' . strtolower($b->test_profile->get_result_scale()) . ' ' . $a->test_profile->get_identifier());
1087	}
1088	public function get_test_run_times()
1089	{
1090		$run_times = array();
1091		foreach($this->get_system_identifiers() as $si)
1092		{
1093			$run_times[$si] = 0;
1094		}
1095		foreach($this->result_objects as &$ro)
1096		{
1097			foreach($ro->get_run_times() as $si => $elapsed_time)
1098			{
1099				$run_times[$si] += $elapsed_time;
1100			}
1101		}
1102
1103		return $run_times;
1104	}
1105	public function sort_result_object_order_by_run_time($asc = false)
1106	{
1107		uasort($this->result_objects, array('pts_result_file', 'result_run_time_comparison'));
1108
1109		if($asc == false)
1110		{
1111			$this->result_objects = array_reverse($this->result_objects, true);
1112		}
1113	}
1114	public static function result_run_time_comparison($a, $b)
1115	{
1116		$a = $a->get_run_time_avg();
1117		$b = $b->get_run_time_avg();
1118
1119		if($a == $b)
1120		{
1121			return 0;
1122		}
1123
1124		return $a < $b ? -1 : 1;
1125	}
1126	public function get_install_log_for_test(&$test_profile, $read_file = false, $cleanse_file = true)
1127	{
1128		// if $read_file is false, index will be returned. if $read_file is -2, will return whether log files simply exist
1129		$files = array();
1130		static $logs_exist_for_test; // caching helper
1131		$test_file_name_chunk = $test_profile->get_identifier_simplified() . '.log';
1132
1133		if($read_file == -2 && isset($logs_exist_for_test[$test_file_name_chunk]))
1134		{
1135			return $logs_exist_for_test[$test_file_name_chunk];
1136		}
1137
1138		if($this->get_test_installation_log_dir() && count($d = pts_file_io::glob($this->get_test_installation_log_dir() . '*/' . $test_file_name_chunk)) > 0)
1139		{
1140			$logs_exist_for_test[$test_file_name_chunk] = true;
1141			if($read_file == -2)
1142			{
1143				return true;
1144			}
1145
1146			foreach($d as $file)
1147			{
1148				$basename_file = basename(dirname($file));
1149				if($read_file !== false && $basename_file == $read_file)
1150				{
1151					$file = file_get_contents($file);
1152					return $cleanse_file ? phodevi_vfs::cleanse_file($file, $basename_file) : $file;
1153				}
1154				$files[] = $basename_file;
1155			}
1156		}
1157		else if($this->get_result_dir() && is_file($this->get_result_dir() . 'installation-logs.zip') && extension_loaded('zip'))
1158		{
1159			$logs_exist_for_test[$test_file_name_chunk] = true;
1160			if($read_file == -2)
1161			{
1162				// TODO: could make this more accurate to ensure a precise match, but could become expensive
1163				return true;
1164			}
1165
1166			$zip = new ZipArchive();
1167			$res = $zip->open($this->get_result_dir() . 'installation-logs.zip');
1168
1169			if($res === true)
1170			{
1171				$search_for_file_name_length = strlen($test_file_name_chunk);
1172				for($i = 0; $i < $zip->numFiles; $i++)
1173				{
1174					$index = $zip->getNameIndex($i);
1175					if(isset($index[$search_for_file_name_length]) && substr($index, (0 - $search_for_file_name_length)) == $test_file_name_chunk)
1176					{
1177						$basename_file = basename(dirname($index));
1178
1179						if($basename_file != null)
1180						{
1181							if($read_file !== false && $basename_file == $read_file)
1182							{
1183								$c = $zip->getFromName($index);
1184								$contents = $cleanse_file ? phodevi_vfs::cleanse_file($c, $basename_file) : $c;
1185								$zip->close();
1186								return $contents;
1187							}
1188							$files[] = $basename_file;
1189						}
1190					}
1191				}
1192				$zip->close();
1193			}
1194		}
1195
1196		$logs_exist_for_test[$test_file_name_chunk] = !empty($files);
1197		if($read_file == -2)
1198		{
1199			return false;
1200		}
1201
1202		return $read_file !== false ? false : $files;
1203	}
1204	public function get_test_run_log_for_result(&$result_object, $read_file = false, $cleanse_file = true)
1205	{
1206		// if $read_file is false, index will be returned. if $read_file is -2, will return whether log files simply exist
1207		$files = array();
1208		static $logs_exist_for_test; // caching helper
1209		$ro_hash = $result_object->get_comparison_hash(true, false);
1210
1211		if($read_file == -2 && isset($logs_exist_for_test[$ro_hash]))
1212		{
1213			return $logs_exist_for_test[$ro_hash];
1214		}
1215
1216		if(($test_log_dir = $this->get_test_log_dir($result_object)) && count($d = pts_file_io::glob($test_log_dir . '*.log')) > 0)
1217		{
1218			$logs_exist_for_test[$ro_hash] = true;
1219			if($read_file == -2)
1220			{
1221				return true;
1222			}
1223
1224			foreach($d as $file)
1225			{
1226				$basename_file = basename($file);
1227				if($read_file !== false && $basename_file == $read_file)
1228				{
1229					$file = file_get_contents($file);
1230					return $cleanse_file ? phodevi_vfs::cleanse_file($file, $basename_file) : $file;
1231				}
1232				$files[] = $basename_file;
1233			}
1234		}
1235		else if($this->get_result_dir() && is_file($this->get_result_dir() . 'test-logs.zip') && extension_loaded('zip'))
1236		{
1237			$logs_exist_for_test[$ro_hash] = true;
1238			if($read_file == -2)
1239			{
1240				// TODO: could make this more accurate to ensure a precise match, but could become expensive
1241				return true;
1242			}
1243
1244			$zip = new ZipArchive();
1245			$res = $zip->open($this->get_result_dir() . 'test-logs.zip');
1246
1247			if($res === true)
1248			{
1249				$log_path = 'test-logs/' . $ro_hash . '/';
1250				$log_path_l = strlen($log_path);
1251				for($i = 0; $i < $zip->numFiles; $i++)
1252				{
1253					$index = $zip->getNameIndex($i);
1254					if(isset($index[$log_path_l]) && substr($index, 0, $log_path_l) == $log_path)
1255					{
1256						$basename_file = substr($index, $log_path_l);
1257						if($basename_file != null)
1258						{
1259							if($read_file !== false && $basename_file == $read_file)
1260							{
1261								$c = $zip->getFromName($index);
1262								$contents = $cleanse_file ? phodevi_vfs::cleanse_file($c, $basename_file) : $c;
1263								$zip->close();
1264								return $contents;
1265							}
1266							$files[] = $basename_file;
1267						}
1268					}
1269				}
1270				$zip->close();
1271			}
1272		}
1273
1274		$logs_exist_for_test[$ro_hash] = !empty($files);
1275		if($read_file == -2)
1276		{
1277			return false;
1278		}
1279
1280		return $read_file !== false ? false : $files;
1281	}
1282}
1283
1284?>
1285