From 53caba5ba7dfb90e0dcee927dda3ad0ed3b5c9d1 Mon Sep 17 00:00:00 2001 From: Chris Alfano Date: Sat, 24 Dec 2022 01:31:14 -0500 Subject: [PATCH 01/14] refactor(phpunit): upgrade test format --- .../emergence.read-only/ActiveRecordTest.php | 6 ++--- .../emergence.read-only/CacheTest.php | 24 +++++++++++++++++++ .../LoadAllClassesTest.php | 4 ++-- .../emergence.read-write/FS/SiteFileTest.php | 12 +++------- 4 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 phpunit-tests/emergence.read-only/CacheTest.php diff --git a/phpunit-tests/emergence.read-only/ActiveRecordTest.php b/phpunit-tests/emergence.read-only/ActiveRecordTest.php index 1eda0c240..d6a1848ae 100644 --- a/phpunit-tests/emergence.read-only/ActiveRecordTest.php +++ b/phpunit-tests/emergence.read-only/ActiveRecordTest.php @@ -1,8 +1,8 @@ -assertFalse(Cache::fetch('test_key'), 'false for non-existent key'); + $this->assertTrue(Cache::store('test_key', 'value'), 'set a value'); + $this->assertEquals('value', Cache::fetch('test_key'), 'value is set'); + $this->assertTrue(Cache::store('null_key', null), 'set a null value'); + $this->assertNull(Cache::fetch('null_key'), 'null value is set'); + $this->assertTrue(Cache::exists('test_key', null), 'value exists'); + $this->assertTrue(Cache::exists('null_key', null), 'null value exists'); + $this->assertFalse(Cache::exists('unset_key', null), 'unset value does not exist'); + $this->assertTrue(Cache::delete('test_key', null), 'delete value'); + $this->assertFalse(Cache::exists('test_key', null), 'deleted value does not exist'); + + $this->assertEquals(1, Cache::increase('counter_key'), 'counter initializes to 1'); + $this->assertEquals(2, Cache::increase('counter_key'), 'counter increases to 2'); + $this->assertEquals(1, Cache::decrease('counter_key'), 'counter decreases to 1'); + + $this->assertEquals(-1, Cache::decrease('negative_counter_key'), 'counter initialized to -1'); + } +} \ No newline at end of file diff --git a/phpunit-tests/emergence.read-only/LoadAllClassesTest.php b/phpunit-tests/emergence.read-only/LoadAllClassesTest.php index a548d2aa2..7a1e1b5a9 100644 --- a/phpunit-tests/emergence.read-only/LoadAllClassesTest.php +++ b/phpunit-tests/emergence.read-only/LoadAllClassesTest.php @@ -1,6 +1,6 @@ -rootNode = SiteCollection::getOrCreateRootCollection('phpunit-test-data'); $this->parentRootNode = SiteCollection::getOrCreateRootCollection('phpunit-test-data', true); From 558945f70abf771c4b205225a0bf01dc6bfe8108 Mon Sep 17 00:00:00 2001 From: Chris Alfano Date: Sat, 24 Dec 2022 01:31:37 -0500 Subject: [PATCH 02/14] feat(phpunit): configure phpunit environment --- phpunit.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 phpunit.xml diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 000000000..6f98f0d87 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,15 @@ + + + + + + + + phpunit-tests/emergence.read-only + + + \ No newline at end of file From 19644fc80baee6e234e5e35b6ce353a69e21f5ce Mon Sep 17 00:00:00 2001 From: Chris Alfano Date: Sat, 24 Dec 2022 01:33:38 -0500 Subject: [PATCH 03/14] feat(ci): add phpunit workflow --- .github/workflows/test-unit.yml | 99 +++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 .github/workflows/test-unit.yml diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml new file mode 100644 index 000000000..8fb58cc4f --- /dev/null +++ b/.github/workflows/test-unit.yml @@ -0,0 +1,99 @@ +name: 'Test: Unit' + +on: pull_request + +env: + HAB_LICENSE: accept-no-persist + HOLO_CACHE_TO: origin + HOLO_CACHE_FROM: origin + +jobs: + test-unit: + + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v2 + + - name: 'Stop default mysql service' + run: sudo service mysql stop + + - name: 'Match system timezone to application default' + run: sudo timedatectl set-timezone America/New_York + + - name: 'Initialize Chef Habitat environment' + uses: JarvusInnovations/habitat-action@action/v1 + timeout-minutes: 5 + env: + HAB_MYSQL: | + app_username = 'appadmin' + app_password = 'appadmin' + bind = '0.0.0.0' + HAB_PHP_RUNTIME: | + [sites.default.holo] + gitDir = '${{ github.workspace }}/.git' + with: + deps: | + jarvus/hologit + emergence/phpunit + supervisor: | + core/mysql + emergence/php-runtime --bind="database:mysql.default" + + - name: Waiting for MySQL + run: | + until nc -z localhost 3306; do + sleep 0.5 + echo -n "." + done + + - name: Waiting for Emergence runtime + run: | + until nc -z localhost 9123; do + sleep 0.5 + echo -n "." + done + + - name: Configure command-line client access + run: | + sudo chmod ugo+xr \ + /hab/svc/mysql/config \ + /hab/svc/mysql/config/client.cnf \ + /hab/svc/php-runtime/config \ + /hab/svc/php-runtime/config/fpm-exec + + ln -sf /hab/svc/mysql/config/client.cnf ~/.my.cnf + + - name: Load site projection into emergence runtime + env: + HOLO_CACHE_FROM: origin + HOLO_CACHE_TO: origin + run: | + SITE_TREE="$(hab pkg exec jarvus/hologit git-holo project emergence-site)" + [ -n "${SITE_TREE}" ] || exit 1 + hab pkg exec emergence/php-runtime emergence-php-load "${SITE_TREE}" + + - name: Run PHPUnit tests + run: | + sudo --preserve-env --user=hab \ + hab pkg exec emergence/phpunit phpunit + + - name: Open access to crash log + if: always() + run: | + sudo chmod ugo+rX /hab/svc/php-runtime/{var,var/logs} || true + sudo chmod ugo+r /hab/svc/php-runtime/var/logs/crash.log || true + + - name: Upload crash log + uses: actions/upload-artifact@v2 + if: always() + with: + name: crash-log + path: /hab/svc/php-runtime/var/logs/crash.log + + - name: Upload supervisor log + uses: actions/upload-artifact@v2 + if: always() + with: + name: supervisor-log + path: /hab/sup/default/sup.log From 01dc29fb5e96c2e6a8aaf563d1b6c52fae680e44 Mon Sep 17 00:00:00 2001 From: Chris Alfano Date: Sat, 24 Dec 2022 06:24:31 -0500 Subject: [PATCH 04/14] fix: avoid use of reserved class name Object --- php-classes/Emergence/ActiveRecord/AbstractActiveRecord.php | 4 ++-- .../Emergence/ActiveRecord/Fields/{Object.php => Obj.php} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename php-classes/Emergence/ActiveRecord/Fields/{Object.php => Obj.php} (94%) diff --git a/php-classes/Emergence/ActiveRecord/AbstractActiveRecord.php b/php-classes/Emergence/ActiveRecord/AbstractActiveRecord.php index 5334f126e..38f20aa2a 100644 --- a/php-classes/Emergence/ActiveRecord/AbstractActiveRecord.php +++ b/php-classes/Emergence/ActiveRecord/AbstractActiveRecord.php @@ -45,7 +45,7 @@ abstract class AbstractActiveRecord implements ActiveRecordInterface ], Fields\Enum::class, Fields\Timestamp::class, - Fields\Object::class + Fields\Obj::class ]; @@ -247,7 +247,7 @@ protected static function initField($field, array $options = []) throw new \Exception("No field handler registered for type '$options[type]'"); } } - + if (!empty($options['fieldHandler'])) { $options['fieldHandler']['class']::initOptions($options); } diff --git a/php-classes/Emergence/ActiveRecord/Fields/Object.php b/php-classes/Emergence/ActiveRecord/Fields/Obj.php similarity index 94% rename from php-classes/Emergence/ActiveRecord/Fields/Object.php rename to php-classes/Emergence/ActiveRecord/Fields/Obj.php index 1b0aedebc..a24da7058 100644 --- a/php-classes/Emergence/ActiveRecord/Fields/Object.php +++ b/php-classes/Emergence/ActiveRecord/Fields/Obj.php @@ -2,7 +2,7 @@ namespace Emergence\ActiveRecord\Fields; -class Object extends AbstractField +class Obj extends AbstractField { public static function getAliases() { From a76cef2d79eb9afbde57ba0b200e5a3279d77314 Mon Sep 17 00:00:00 2001 From: Chris Alfano Date: Sat, 24 Dec 2022 06:42:28 -0500 Subject: [PATCH 05/14] fix: avoid use of reserved class name String --- php-classes/Emergence/ActiveRecord/AbstractActiveRecord.php | 2 +- .../Emergence/ActiveRecord/Fields/{String.php => Str.php} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename php-classes/Emergence/ActiveRecord/Fields/{String.php => Str.php} (94%) diff --git a/php-classes/Emergence/ActiveRecord/AbstractActiveRecord.php b/php-classes/Emergence/ActiveRecord/AbstractActiveRecord.php index 38f20aa2a..dd5db7fed 100644 --- a/php-classes/Emergence/ActiveRecord/AbstractActiveRecord.php +++ b/php-classes/Emergence/ActiveRecord/AbstractActiveRecord.php @@ -38,7 +38,7 @@ abstract class AbstractActiveRecord implements ActiveRecordInterface public static $fieldHandlers = [ Fields\Integer::class, - Fields\String::class, + Fields\Str::class, Fields\Text::class => [ 'foo' => 'bar', 'boo' => 'baz' diff --git a/php-classes/Emergence/ActiveRecord/Fields/String.php b/php-classes/Emergence/ActiveRecord/Fields/Str.php similarity index 94% rename from php-classes/Emergence/ActiveRecord/Fields/String.php rename to php-classes/Emergence/ActiveRecord/Fields/Str.php index f059c5b5c..731088d5c 100644 --- a/php-classes/Emergence/ActiveRecord/Fields/String.php +++ b/php-classes/Emergence/ActiveRecord/Fields/Str.php @@ -2,7 +2,7 @@ namespace Emergence\ActiveRecord\Fields; -class String extends AbstractField +class Str extends AbstractField { public static function getAliases() { From 8cf75aedcaf2a37e2212b20dbf4d51e1360725e0 Mon Sep 17 00:00:00 2001 From: Chris Alfano Date: Sat, 24 Dec 2022 06:52:42 -0500 Subject: [PATCH 06/14] chore(phpunit): add .phpunit.result.cache to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index af8d44b5d..c4cd27e8f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ .php_cs.cache +.phpunit.result.cache From d266b67c7b8428c5d679acb9a8f5074bc57a7fab Mon Sep 17 00:00:00 2001 From: Chris Alfano Date: Sat, 24 Dec 2022 06:55:35 -0500 Subject: [PATCH 07/14] chore: delete FLV classes They have syntax errors that newer PHP versions error on, and aren't being used anywhere --- php-classes/AMF0Parser.class.php | 762 ------------------------------- php-classes/Flvinfo.class.php | 696 ---------------------------- 2 files changed, 1458 deletions(-) delete mode 100644 php-classes/AMF0Parser.class.php delete mode 100644 php-classes/Flvinfo.class.php diff --git a/php-classes/AMF0Parser.class.php b/php-classes/AMF0Parser.class.php deleted file mode 100644 index 5409a0b2c..000000000 --- a/php-classes/AMF0Parser.class.php +++ /dev/null @@ -1,762 +0,0 @@ - - * @access public - * @param string $str Optional initialization string - * @return AMF0Parser - */ - public function AMF0Parser($str = false) - { - /** - * Proceed to endianess detection. This will be needed by - * double decoding because unpack doesn't allow the selection - * of endianess when decoding doubles. - */ - - // Pack 1 in machine order - $tmp = pack("L", 1); - - // Unpack it in big endian order - $tmp2 = unpack("None",$tmp); - - // Check if it's still one - if ($tmp2['one'] == 1) { - $this->endian = 0x00; - } // Yes, big endian - else { - $this->endian = 0x01; - } // No, little endian - - // Initialize if needed - if ($str !== false) { - $this->initialize($str); - } - } // Constructor - - /** - * Initialize data - * - * @author Tommy Lacroix - * @access public - * @param string $str AMF0 Data - */ - public function initialize($str) - { - $this->data = $str; - $this->dataLength = strlen($str); - $this->index = 0; - } // initialize function - - - /** - * Read all packets - * - * @author Tommy Lacroix - * @access public - * @param string $str AMF0 data (optional, uses the initialized one if not given) - * @return array - */ - public function readAllPackets($str = false) - { - // Initialize if needed - if ($str !== false) { - $this->initialize($str); - } - - // Parse each packet - $ret = array(); - while ($this->index < $this->dataLength) { - $ret[] = $this->readPacket(); - } - - // Return it - return $ret; - } // readAllPackets function - - /** - * Read a packet at current index - * - * @author Tommy Lacroix - * @access public - * @return mixed - */ - public function readPacket() - { - // Get data code - $dataType = ord($this->data[$this->index++]); - // Interpret - switch ($dataType) { - case self::TYPE_NUMBER: // Number 0x00 - return $this->readNumber(); - case self::TYPE_BOOLEAN: // Boolean 0x01 - return $this->readBoolean(); - case self::TYPE_STRING: // String 0x02 - return $this->readString(); - case self::TYPE_OBJECT: // Object 0x03 - return $this->readObject(); - case self::TYPE_MOVIECLIP: // MovieClip - throw new Exception("Unhandled AMF type: MovieClip (04)"); - break; - case self::TYPE_NULL: // NULL 0x05 - return NULL; - case self::TYPE_UNDEFINED: // Undefined 0x06 - return 'undefined'; - case self::TYPE_REFERENCE: // Reference - throw new Exception("Unhandled AMF type: Reference (07)"); - break; - case self::TYPE_MIXEDARRAY : // Mixed array 0x08 - return $this->readMixedArray(); - case self::TYPE_OBJECT_TERM: // ObjectTerm - throw new Exception("Unhandled AMF type: ObjectTerm (09) -- should only happen in the getObject function"); - break; - case self::TYPE_ARRAY: // Array 0x0a - return $this->readArray(); - case self::TYPE_DATE: // Date - return $this->readDate(); - case self::TYPE_LONGSTRING: // LongString - return $this->readLongString(); - case TYPE_RECORDSET: // Recordset - throw new Exception("Unhandled AMF type: Unsupported (0E)"); - break; - case self::TYPE_XML: // XML - return $this->readLongString(); - case self::TYPE_TYPED_OBJECT: // Typed Object - return $this->readTypedObject(); - case TYPE_AMF3: // AMF3 - throw new Exception("Unhandled AMF type: AMF3 (11)"); - break; - default: - throw new Exception("Unhandled AMF type: unknown (0x".dechex($dataType).") at offset ".$this->index-1); - } - } // readPacket function - - /** - * Read a string at current index - * - * @author Tommy Lacroix - * @access public - * @return string - */ - public function readString() - { - // Check if we have enough data - if (strlen($this->data) < $this->index+2) { - return null; - } - - // Get length - $len = unpack('nlen', substr($this->data,$this->index,2)); - $this->index+=2; - - // Get string - $val = substr($this->data, $this->index, $len['len']); - $this->index += $len['len']; - - // Return it - return $val; - } // readString function - - /** - * Read a long string at current index - * - * @author Tommy Lacroix - * @access public - * @return string - */ - public function readLongString() - { - // Check if we have enough data - if (strlen($this->data) < $this->index+4) { - return null; - } - - // Get length - $len = unpack('Nlen', substr($this->data,$this->index,4)); - $this->index+=4; - - // Get string - $val = substr($this->data, $this->index, $len['len']); - $this->index += $len['len']; - - // Return it - return $val; - } // readLongString function - - /** - * Read a number (double) at current index - * - * @author Tommy Lacroix - * @access public - * @return double - */ - public function readNumber() - { - // Check if we have enough data - if (strlen($this->data) < $this->index+8) { - return null; - } - - // Get the packet, big endian (8 bytes long) - $packed = substr($this->data, $this->index, 8); - $this->index += 8; - - // Reverse it if we're little endian - if ($this->endian == 0x01) { - $packed = strrev($packed); - } - - // Unpack it - $tmp = unpack("dnumber", $packed); - - // Return it - return $tmp['number']; - } // readNumber function - - /** - * Read a boolean at current index - * - * @author Tommy Lacroix - * @access public - * @return boolean - */ - public function readBoolean() - { - return ord($this->data[$this->index++]) == 1; - } // readBoolean function - - /** - * Read an object at current index - * - * @author Tommy Lacroix - * @access public - * @return stdClass - */ - public function readObject() - { - // Create return object we will add data to - $ret = new stdClass(); - - do { - // Get key - $key = $this->readString(); - - // Stop if no key was read (end of AMF0 stream) - if ($key === null) { - break; - } - - // Check if we reached ObjectTerm (09) - $dataType = ord($this->data[$this->index]); - - // If it's not an Object Term, read the packet - if ($dataType != self::TYPE_OBJECT_TERM) { - // Get data - $val = $this->readPacket(); - - // Store it - $ret->$key = $val; - } - } while ($dataType != 0x09); - - // Skip the Object Term - $this->index += 1; - - // Return object - return $ret; - } // readObject function - - /** - * Read a typed object at current index - * - * @author Tommy Lacroix - * @access public - * @return stdClass - */ - public function readTypedObject() - { - // Get class name - $className = $this->readString(); - - // Get object - $object = $this->readObject(); - - // Save class name inside object - $object->__className = $className; - - // Return object - return $object; - } // readTypedObject function - - /** - * Read a mixed array at current position - * - * Note: A mixed array is basically an object, but with a long integer describing its highest index at first. - * - * @author Tommy Lacroix - * @access public - * @return array - */ - public function readMixedArray() - { - // Skip the index - $this->index += 4; - - // Parse the object, but return it as an array - return get_object_vars($this->readObject()); - } // readMixedArray function - - /** - * Get an indexed array ([0,1,2,3,4,...]) - * - * @author Tommy Lacroix - * @access public - * @return array - */ - public function readArray() - { - // Check if we have enough data - if (strlen($this->data) < $this->index+4) { - return null; - } - - // Get item count - $len = unpack('Nlen',substr($this->data,$this->index,4)); - $this->index+=4; - - // Get each packet - $ret = array(); - for ($i=0;$i<$len['len'];$i++) { - $ret[] = $this->readPacket(); - } - - // Return the array - return $ret; - } // readArray function - - /** - * Read a date at current index - * - * @author Tommy Lacroix - * @access public - * @return string - */ - public function readDate() - { - // Check if we have enough data - if (strlen($this->data) < $this->index+10) { - return null; - } - - // Get the packet, big endian (8 bytes long) - $packed = substr($this->data, $this->index, 8); - $this->index += 8; - - // Reverse it if we're little endian - if ($this->endian == 0x01) { - $packed = strrev($packed); - } - - // Unpack it - $tmp = unpack("dnumber", $packed); - $epoch = $tmp['number']/1000; - - // Get timezone - $tmp = unpack('nnumber', substr($this->data, $this->index, 2)); - $this->index += 2; - $timezone = $tmp['number']; - if ($timezone > 32767) { - $timezone = $timezone-65536; - } - - // Make epoch GMT, and then convert to local time - $time = $epoch; - $time += $timezone*60; // Timezone is in seconds - $time += date('Z',$time); - - // Return it - return date('r',$time); - } // readDate function - - - /** - * Write a packet - * - * @author Tommy Lacroix - * @access public - * @param mixed $value - * @param int $type autodetected if none given - * @return string - */ - public function writePacket($value, $type=false) - { - if ($type === false) { - if (($value === true) || ($value === false)) { - $type = self::TYPE_BOOLEAN; - } - if (is_numeric($value)) { - $type = self::TYPE_NUMBER; - } elseif (is_array($value)) { - $type = self::TYPE_ARRAY; - foreach (array_keys($value) as $k) { - if (preg_match(',[^0-9],',$k)) { - $type = self::TYPE_MIXEDARRAY; - break; - } - } - // Test for mixed/indexed - } elseif (is_object($value)) { - $type = self::TYPE_OBJECT; - } elseif (is_string($value)) { - if (strlen($value) < 65535) { - $type = self::TYPE_STRING; - } else { - $type = self::TYPE_LONGSTRING; - } - } elseif (is_null($value)) { - $type = self::TYPE_NULL; - } - } // if($type===false) - - switch ($type) { - case self::TYPE_NUMBER: - return $this->writeNumber($value); - case self::TYPE_STRING: - return $this->writeString($value); - case self::TYPE_LONGSTRING: - return $this->writeLongString($value); - case self::TYPE_NULL: - return $this->writeNull(); - case self::TYPE_BOOLEAN: - return $this->writeBoolean($value); - case self::TYPE_ARRAY: - return $this->writeArray($value); - case self::TYPE_MIXEDARRAY: - return $this->writeMixedArray($value); - case self::TYPE_OBJECT: - return $this->writeObject($value); - default: - throw new Exception('Unhandled AMF0 type: 0x'.dechex($type)); - } //switch($type) - } // writePacket function - - /** - * Write a string - * - * @author Tommy Lacroix - * @access public - * @param string $str - * @return string - */ - public function writeString($str) - { - // Write type - $value = chr(self::TYPE_STRING); - - // Write length - $value .= pack('n', strlen($str)); - - // Write string - $value .= $str; - - // Return it - return $value; - } - - /** - * Write a long string - * - * @author Tommy Lacroix - * @access public - * @param string $str - * @return string - */ - public function writeLongString($str) - { - // Write type - $value = chr(self::TYPE_LONGSTRING); - - // Write length - $value .= pack('N', strlen($str)); - - // Write string - $value .= $str; - - // Return it - return $value; - } // writeLongString function - - /** - * Write a XML - * - * @author Tommy Lacroix - * @access public - * @param string $str - * @return string - */ - public function writeXML($str) - { - // Write type - $value = chr(self::TYPE_XML); - - // Write length - $value .= pack('N', strlen($str)); - - // Write string - $value .= $str; - - // Return it - return $value; - } // writeXML function - - /** - * Write a number - * - * @author Tommy Lacroix - * @access public - * @param integer $number - * @return string - */ - public function writeNumber($number) - { - // Write type - $value = chr(self::TYPE_NUMBER); - - // Build packed - $packed = pack('d', $number); - - // Reverse it if we're little endian - if ($this->endian == 0x01) { - $packed = strrev($packed); - } - - // Write packed - $value .= $packed; - - // Return it - return $value; - } // writeNumber function - - - /** - * Write a null - * - * @author Tommy Lacroix - * @access public - * @return string - */ - public function writeNull() - { - // Write type - $value = chr(self::TYPE_NULL); - - // Return it - return $value; - } // writeNull function - - /** - * Write a boolean - * - * @author Tommy Lacroix - * @access public - * @param bool $boolean - * @return string - */ - public function writeBoolean($boolean) - { - // Write type - $value = chr(self::TYPE_BOOLEAN); - - // Write value - $value .= ($boolean ? chr(1) : chr(0)); - - // Return it - return $value; - } // writeBoolean function - - /** - * Write a mixed array - * - * @author Tommy Lacroix - * @access public - * @param array $array - * @return string - */ - public function writeMixedArray($array) - { - // Write type - $value = chr(self::TYPE_MIXEDARRAY); - - // Write index - $value .= pack('N',count($array)); - - // Write as object - $value .= $this->writeObjectSub($array); - - // Return it - return $value; - } // writeMixedArray - - /** - * Write an object - * - * @author Tommy Lacroix - * @access public - * @param stdClass|array $object - * @return string - */ - public function writeObject($object) - { - // Write type - $value = chr(self::TYPE_OBJECT); - - // Write as object - $value .= $this->writeObjectSub(get_object_vars($object)); - - // Return it - return $value; - } // writeObject function - - /** - * Write a typed object - * - * @author Tommy Lacroix - * @access public - * @param stdClass|array $object - * @return string - */ - public function writeTypedObject($object, $className = false) - { - // Write type - $value = chr(self::TYPE_TYPED_OBJECT); - - // Write class - if ($className === false) { - if (isset($object->__className)) { - $className = $object->__className; - } else { - $className = get_class($object); - } - } - $value .= $this->writeString($className); - - // Write as object - $value .= $this->writeObjectSub(get_object_vars($object)); - - // Return it - return $value; - } // writeTypedObject function - - /** - * Write an object, without the leading type - * - * @author Tommy Lacroix - * @access private - * @internal - * @param stdClass|array $object - * @return string - */ - private function writeObjectSub($object) - { - $output = ''; - - // Write each element - foreach ($object as $key=>$value) { - // Write key - $output .= pack('n',strlen($key)).$key; - - // Write value - $output .= $this->writePacket($value); - } - - // Write object term - $output .= pack('n',0); - $output .= chr(0x09); - - // Return it - return $output; - } // writeObjectSub function - - /** - * Write an index array - * - * @author Tommy Lacroix - * @access public - * @param array $array - * @return string - */ - public function writeArray($array) - { - // Write type - $value = chr(self::TYPE_ARRAY); - - // Write length - $value .= pack('N', count($array)); - - // Write elements - foreach ($array as $value) { - $value .= $this->writePacket($value); - } - - // Return it - return $value; - } // writeArray function - } \ No newline at end of file diff --git a/php-classes/Flvinfo.class.php b/php-classes/Flvinfo.class.php deleted file mode 100644 index a4ed9705b..000000000 --- a/php-classes/Flvinfo.class.php +++ /dev/null @@ -1,696 +0,0 @@ - - * @access public - * @return FLVInfo2 - */ - public function __construct() - { - } // Constructor - - /** - * Get information about the FLV file - * - * @author Tommy Lacroix - * @access public - * - * - * $flvinfo = new FLVInfo2(); - * $info = $flvinfo->getInfo('demo.flv',true); - * var_export($info); - * - * - * Demo output: - * - * - * ( - * [signature] => 1 - * [hasVideo] => 1 - * [hasAudio] => 1 - * [minimalFlashVersion] => 8 - * [duration] => 32.4 - * [video] => stdClass Object ( - * [codec] => 4 - * [width] => 480 - * [height] => 360 - * [bitrate] => 896 - * [fps] => 15 - * [codecStr] => On2 VP6 - * ) - * - * [audio] => stdClass Object ( - * [codec] => 2 - * [frequency] => 44 - * [depth] => 16 - * [channels] => 2 - * [bitrate] => 128 - * [codecStr] => MP3 - * ) - * - * ) - * - * - * @param string $filename - * @param bool $extended Get extended information about the FLV file - * @return stdClass - */ - public function getInfo($filename, $extended=false, $useMeta=true) - { - // Initialize info class where we'll collect preliminary data - $info = new stdClass(); - - // No tag found yet - $gotVideo = $gotAudio = false; - $tagParsed = 0; - - // Frames types - $vFrames = array(); - $aFrames = array(); - - // Max tags - $maxTags = $extended ? false : 20; - - // Open file - $f = fopen($filename,'rb'); - - // Read header - $buf = fread($f, 9); - $header = unpack('C3signature/Cversion/Cflags/Noffset', $buf); - - // Check signature - $info->signature = (($header['signature1'] == 70) && ($header['signature2'] == 76) && ($header['signature3'] == 86)); - - // If signature is valid, go on - if ($info->signature) { - // Version - $info->version = $header['version']; - - // Content - $info->hasVideo = ($header['flags'] & 1) != 0; - $info->hasAudio = ($header['flags'] & 4) != 0; - $hasMeta = false; - - // Read tags - fseek($f, $header['offset']); - //$prevTagSize = 0; - do { - // Read tag header and check length - $buf = fread($f, 15); - if (strlen($buf) < 15) { - break; - } - - // Interpret header - $tagInfo = unpack('Nprevsize/C1type/C3size/C4timestamp/C3stream', $buf); - $tagInfo['size'] = ($tagInfo['size1'] << 16) + ($tagInfo['size2'] << 8) + ($tagInfo['size3']); - $tagInfo['stream'] = ($tagInfo['stream1'] << 16) + ($tagInfo['stream2'] << 8) + $tagInfo['stream3']; - $tagInfo['timestamp'] = ($tagInfo['timestamp1'] << 16) + ($tagInfo['timestamp2'] << 8) + $tagInfo['timestamp3'] + ($tagInfo['timestamp4'] << 24); - - // Validate previous offset - //if ($tagInfo['prevsize'] != $prevTagSize) { - // Do nothing - //} - - // Read tag body - $nextOffset = ftell($f) + $tagInfo['size']; - if ($tagInfo['size'] > 0) { - $body = fread($f, min($tagInfo['size'],16384)); - } else { - $body = ''; - } - - // Seek - fseek($f, $nextOffset); - if ($body == '') { - continue; - } - - // Intepret body - switch ($tagInfo['type']) { - case 0x09: // Video tag - // Mark video frame as found - $gotVideo = true; - - // Unpack flags - $bodyInfo = unpack('Cflags', $body); - - // Get codec - $info->videoCodec = $bodyInfo['flags'] & 15; - - // Get frame type and store it - $frameType = ($bodyInfo['flags'] >> 4) & 15; - $vFrames[] = array('type'=>$frameType, 'timestamp'=>$tagInfo['timestamp'], 'size'=>$tagInfo['size']); - - if (((!isset($frameWidth)) && (!isset($frameHeight))) && ($frameType == 0x01)) { - switch ($bodyInfo['flags'] & 15) { - case self::FLV_VIDEO_CODEC_ON2_VP6: - case self::FLV_VIDEO_CODEC_ON2_VP6ALPHA: - // This probably isn't right - // in all cases (VP60, VP61, VP62) - // http://wiki.multimedia.cx/index.php?title=On2_VP6 - $frameWidth = ord($body[5])*16; - $frameHeight = ord($body[6])*16; - break; - case self::FLV_VIDEO_CODEC_SORENSON_H263: - $bin = ''; - for ($i=0;$i<16;$i++) { - $sbin = decbin(ord($body[$i+1])); - $bin .= str_pad($sbin, 8, '0', STR_PAD_LEFT); - } - - // Start code - //$startCode = substr($bin,0,17); - - // Size type - $size = bindec(substr($bin,30,3)); - - // Get width/height - switch ($size) { - case 0: // Custom, 8 bit - $frameWidth = bindec(substr($bin,33,8)); - $frameHeight = bindec(substr($bin,41,8)); - break; - case 1: // Custom, 16 bit - $frameWidth = bindec(substr($bin,33,16)); - $frameHeight = bindec(substr($bin,49,16)); - break; - case 2: - $frameWidth = 352; - $frameHeight = 288; - break; - case 3: - $frameWidth = 176; - $frameHeight = 144; - break; - case 4: - $frameWidth = 128; - $frameHeight = 96; - break; - case 5: - $frameWidth = 320; - $frameHeight = 240; - break; - case 6: - $frameWidth = 160; - $frameHeight = 120; - break; - } - break; - } - } - break; - case 0x08: // Audio tag - // Mark audio frame as found - $gotAudio = true; - - // Unpack flag - $bodyInfo = unpack('Cflags', $body); - - // Get codec - $info->audioCodec = ($bodyInfo['flags'] >> 4) & 15; - - // Get frequency - $freq = ($bodyInfo['flags'] >> 2) & 3; - switch ($freq) { - case 0: $info->audioFreq = 5; break; - case 1: $info->audioFreq = 11; break; - case 2: $info->audioFreq = 22; break; - case 3: $info->audioFreq = 44; break; - }; - - // Get depth (8 or 16 bits) - $info->audioDepth = (($bodyInfo['flags'] >> 1) & 1) == 1 ? 16 : 8; - - // Get channels count - $info->audioChannels = (($bodyInfo['flags'] & 1) == 1) ? 2 : 1; - - // Get frame type and store it - $aFrames[] = array('timestamp'=>$tagInfo['timestamp'], 'size'=>$tagInfo['size']); - break; - case 0x12: // Meta tag - // Skip if already found - if (((isset($hasMeta)) && ($hasMeta == true)) || ($useMeta == false)) { - continue; - } - - // Mark meta as found - $hasMeta = true; - - // Initialize parser - $parser = new AMF0Parser(); - - // Parse data - $meta = $parser->readAllPackets($body); - - // Save it - if ((is_array($meta)) && (isset($meta[1]))) { - $info->meta = $meta; - } - break; - } - - // Increase parsed tag count - $tagParsed++; - } while ( - /* (feof($f) == false) && - (($gotVideo == false) || ($gotAudio == false) || ($gotMeta == false) && (count($vFrames) < 1000)) && - ($tagParsed <= $maxTags)*/ - (!feof($f)) && (($maxTags === false) || ($maxTags < $tagParsed)) - ); - } - - // Close file - fclose($f); - - // Return final object - $ret = new stdClass(); - - // Copy root properties - $ret->signature = $info->signature; - $ret->hasVideo = $info->hasVideo; - $ret->hasAudio = $info->hasAudio; - $ret->minimalFlashVersion = 7; // Default value, changed later by video codec - if ((isset($info->meta[1])) && (isset($info->meta[1]['duration']))) { - $ret->duration = $info->meta[1]['duration']; - } - - // Copy video properties - if ($ret->hasVideo) { - $ret->video = new stdClass(); - - // Trust the parsed video tag for codec - if ($gotVideo) { - // Got one, use it - $ret->video->codec = $info->videoCodec; - } - - // Width - if (isset($frameWidth)) { - $ret->video->width = $frameWidth; - } elseif ((isset($info->meta)) && (isset($info->meta[1])) && (isset($info->meta[1]['width']))) { - $ret->video->width = $info->meta[1]['width']; - } - - // Height - if (isset($frameHeight)) { - $ret->video->height = $frameHeight; - } elseif ((isset($info->meta)) && (isset($info->meta[1])) && (isset($info->meta[1]['height']))) { - $ret->video->height = $info->meta[1]['height']; - } - - // Set information from meta - if ((isset($info->meta)) && (isset($info->meta[1]))) { - // Codec (if not found from video frame) - if (!isset($ret->video->codec)) { - $ret->video->codec = $info->meta[1]['videocodecid']; - } - - - // Bitrate - if (isset($info->meta[1]['videodatarate'])) { - $ret->video->bitrate = $info->meta[1]['videodatarate']; - } - - // FPS - if (isset($info->meta[1]['framerate'])) { - $ret->video->fps = $info->meta[1]['framerate']; - } - - // Cue points - if (isset($info->meta[1]['cuePoints'])) { - $ret->video->cuepoints = $info->meta[1]['cuePoints']; - } - } - - // Key frame ratio, FPS and bit rate - $lastKeyFrame = $lastTimestamp = false; - $keyFrameSum = $keyFrameCount = 0; - $frameSum = $frameCount = $frameSizeSum = 0; - foreach ($vFrames as $idx=>$frame) { - if ($frame['type'] == 0x01) { - if ($lastKeyFrame !== false) { - $keyFrameSum += $idx-$lastKeyFrame; - $keyFrameCount++; - } - - $lastKeyFrame = $idx; - } - - if ($lastTimestamp !== false) { - $frameSum += $frame['timestamp']-$lastTimestamp; - $frameCount++; - } - $lastTimestamp = $frame['timestamp']; - $frameSizeSum += $frame['size']; - } - if (($keyFrameSum > 0) && ($keyFrameCount > 0)) { - $ret->video->keyframeRatio = 1/($keyFrameSum/$keyFrameCount); - $ret->video->keyframeEvery = ($keyFrameSum/$keyFrameCount); - } - if (($frameSum > 0) && ($frameCount > 0) && (!isset($ret->video->fps))) { - $ret->video->fps = round(1000/($frameSum/$frameCount)); - } - if (($frameSizeSum > 0) && ($frameCount > 0) && (!isset($ret->video->bitrate))) { - $ret->video->bitrate = round($frameSizeSum/$frameSum)*8; - } - } - - // Copy audio properties - if ($ret->hasAudio) { - $ret->audio = new stdClass(); - - // Trust the parsed audio tag - if ($gotAudio) { - // Got one, use it - $ret->audio->codec = $info->audioCodec; - $ret->audio->frequency = $info->audioFreq; - $ret->audio->depth = $info->audioDepth; - $ret->audio->channels = $info->audioChannels; - } - - // Set information from meta - if (isset($info->meta[1])) { - // Codec (if not found from video frame) - if (!isset($ret->audio->codec)) { - $ret->audio->codec = $info->meta[1]['audiocodecid']; - } - - // Bitrate - if (isset($info->meta[1]['audiodatarate'])) { - $ret->audio->bitrate = $info->meta[1]['audiodatarate']; - } - } - - // Get bitrate if not specified - if (!isset($ret->audio->bitrate)) { - $lastTimestamp = false; - $frameSum = $frameCount = $frameSizeSum = 0; - foreach ($aFrames as $idx=>$frame) { - if ($lastTimestamp !== false) { - $frameSum += $frame['timestamp']-$lastTimestamp; - $frameCount++; - } - $lastTimestamp = $frame['timestamp']; - $frameSizeSum += $frame['size']; - } - if (($frameSizeSum > 0) && ($frameCount > 0)) { - $ret->audio->bitrate = round($frameSizeSum/$frameSum)*8; - } - } - } - - // Get strings for audio/video codecs - if ((isset($ret->video)) && (isset($ret->video->codec))) { - switch ($ret->video->codec) { - case self::FLV_VIDEO_CODEC_SORENSON_H263: - $ret->video->codecStr = 'Sorenson H263'; - $ret->minimalFlashVersion = 7; - break; - case self::FLV_VIDEO_CODEC_SORENSON: - $ret->video->codecStr = 'Sorenson'; - $ret->minimalFlashVersion = 7; - break; - case self::FLV_VIDEO_CODEC_ON2_VP6: - $ret->video->codecStr = 'On2 VP6'; - $ret->minimalFlashVersion = 8; - break; - case self::FLV_VIDEO_CODEC_ON2_VP6ALPHA: - $ret->video->codecStr = 'On2 VP6 Alpha'; - $ret->minimalFlashVersion = 8; - break; - case self::FLV_VIDEO_CODEC_SCREENVIDEO_2: - $ret->video->codecStr = 'Screen Video 2'; - $ret->minimalFlashVersion = 7; - break; - } - } - - if ((isset($ret->audio)) && (isset($ret->audio->codec))) { - switch ($ret->audio->codec) { - case self::FLV_AUDIO_CODEC_UNCOMPRESSED: - $ret->audio->codecStr = 'Uncompressed'; - break; - case self::FLV_AUDIO_CODEC_ADPCM: - $ret->audio->codecStr = 'ADPCM'; - break; - case self::FLV_AUDIO_CODEC_MP3: - $ret->audio->codecStr = 'MP3'; - break; - case self::FLV_AUDIO_CODEC_NELLYMOSER_8K: - case self::FLV_AUDIO_CODEC_NELLYMOSER: - $ret->audio->codecStr = 'Nellymoser'; - break; - } - } - - $ret->rawMeta = isset($info->meta) ? $info->meta : null; - - // Return the object - return $ret; - } // getInfo method - - /** - * Get a files meta data and cuepoints - * - * @author Tommy Lacroix - * @access public - * @param string $filename - * @return array(metas=>array(...),cuepoints=>array(...)) - */ - public function getMeta($filename) - { - // Open file - $f = fopen($filename,'rb'); - - // Read header - $buf = fread($f, 9); - $header = unpack('C3signature/Cversion/Cflags/Noffset', $buf); - - // Check signature - $signature = (($header['signature1'] == 70) && ($header['signature2'] == 76) && ($header['signature3'] == 86)); - - // If signature is valid, go on - $cuepoints = $metas = array(); - if ($signature) { - // Read tags - fseek($f, $header['offset']); - //$prevTagSize = 0; - do { - // Read tag header and check length - $buf = fread($f, 15); - if (strlen($buf) < 15) { - break; - } - - // Interpret header - $tagInfo = unpack('Nprevsize/C1type/C3size/C4timestamp/C3stream', $buf); - $tagInfo['size'] = ($tagInfo['size1'] << 16) + ($tagInfo['size2'] << 8) + ($tagInfo['size3']); - $tagInfo['stream'] = ($tagInfo['stream1'] << 16) + ($tagInfo['stream2'] << 8) + $tagInfo['stream3']; - $tagInfo['timestamp'] = ($tagInfo['timestamp1'] << 16) + ($tagInfo['timestamp2'] << 8) + $tagInfo['timestamp3'] + ($tagInfo['timestamp4'] << 24); - - // Validate previous offset - //if ($tagInfo['prevsize'] != $prevTagSize) { - // Do nothing - //} - - // Read tag body (max 16k) - $nextOffset = ftell($f) + $tagInfo['size']; - $body = fread($f, min($tagInfo['size'],16384)); - - // Seek - fseek($f, $nextOffset); - - // Intepret body - switch ($tagInfo['type']) { - case 0x09: // Video tag - break; - case 0x08: // Audio tag - break; - case 0x12: // Meta tag - // Initialize parser - $parser = new AMF0Parser(); - - // Parse data - $meta = $parser->readAllPackets($body); - - switch ($meta[0]) { - case 'onMetaData': - $metas[] = $meta; - break; - case 'onCuePoint': - $cuepoints[] = $meta; - break; - } - - // Save it - break; - } - } while ( - (feof($f) == false) - ); - } - - // Close file - fclose($f); - - return array('metas'=>$metas,'cuepoints'=>$cuepoints); - } // getMeta method - - /** - * Rewrite (or strip) meta data and cuepoints from FLV file - * - * @author Tommy Lacroix - * @access public - * @param string $in Input file - * @param string $out Output file (must be different!) - * @param string[] $meta Meta data (ie. array('width'=>320,'height'=>240,...), optional - * @param array $cuepoints Cuepoints (ie. array(array('name'=>'cue1','time'=>'4.4',type=>'event'),...), optional - */ - public function rewriteMeta($in, $out, $meta = false, $cuepoints = false) - { - // No tag found yet - $tagParsed = 0; - - // Open input file - $f = fopen($in,'rb'); - - // Read header - $buf = fread($f, 9); - $header = unpack('C3signature/Cversion/Cflags/Noffset', $buf); - - // Check signature - $signature = (($header['signature1'] == 70) && ($header['signature2'] == 76) && ($header['signature3'] == 86)); - - // If signature is valid, go on - if ($signature) { - // Open output file, and write header - $o = fopen($out,'w'); - if (!$o) { - throw new Exception('Cannot open output file!'); - } - fwrite($o, $buf, 9); - - // Version - //$version = $header['version']; - - // Write meta data - if ($meta !== false) { - $parser = new AMF0Parser(); - $metadata = $parser->writeString('onMetaData'); - $metadata .= $parser->writeMixedArray($meta); - $metadataSize = substr(pack('N',strlen($metadata)),1); - - // Write - $metadataHead = pack('N',0). - chr(18). - $metadataSize. - pack('N',0). - chr(0).chr(0).chr(0); - fwrite($o, $metadataHead); - fwrite($o, $metadata); - } - - // Read tags - //$prevTagSize = 0; - $lastTimeStamp = 0; - do { - // Read tag header and check length - $buf = fread($f, 15); - if (strlen($buf) < 15) { - break; - } - - // Interpret header - $tagInfo = unpack('Nprevsize/C1type/C3size/C4timestamp/C3stream', $buf); - $tagInfo['size'] = ($tagInfo['size1'] << 16) + ($tagInfo['size2'] << 8) + ($tagInfo['size3']); - $tagInfo['stream'] = ($tagInfo['stream1'] << 16) + ($tagInfo['stream2'] << 8) + $tagInfo['stream3']; - $tagInfo['timestamp'] = ($tagInfo['timestamp1'] << 16) + ($tagInfo['timestamp2'] << 8) + $tagInfo['timestamp3'] + ($tagInfo['timestamp4'] << 24); - - // Need we insert a cuepoint here? - if ($cuepoints !== false) { - foreach ($cuepoints as $index=>$cuepoint) { - if (($lastTimeStamp < $cuepoint['time']*1000) && ($tagInfo['timestamp'] >= $cuepoint['time']*1000)) { - // Write cuepoint - $parser = new AMF0Parser(); - $metadata = $parser->writeString('onCuePoint'); - $metadata .= $parser->writeMixedArray($cuepoint); - $metadataSize = substr(pack('N',strlen($metadata)),1); - - // Write first - $metadataHead = pack('N',$tagInfo['prevsize']). - chr(18). - $metadataSize. - pack('N',0). - chr(0).chr(0).chr(0); - fwrite($o, $metadataHead); - fwrite($o, $metadata); - - // Update prevsize in $buf - // @todo - - // Unset cuepoint - unset($cuepoints[$index]); - } - } - } - - // Validate previous offset - //if ($tagInfo['prevsize'] != $prevTagSize) { - // Do nothing - //} - - // Read tag body - $body = fread($f, $tagInfo['size']); - - // Intepret body - switch ($tagInfo['type']) { - case 0x09: // Video tag - // Write to output file - fwrite($o,$buf,15); - fwrite($o,$body,$tagInfo['size']); - break; - case 0x08: // Audio tag - // Write to output file - fwrite($o,$buf,15); - fwrite($o,$body,$tagInfo['size']); - break; - case 0x12: // Meta tag - // Skip - break; - } - - // Increase parsed tag count - $tagParsed++; - } while ( - (feof($f) == false) - ); - } - - // Close file - fclose($f); - fclose($o); - } // rewriteMeta method - } \ No newline at end of file From 5104def816196b83173f276b5524fb5ac55071fd Mon Sep 17 00:00:00 2001 From: Chris Alfano Date: Sat, 24 Dec 2022 07:02:06 -0500 Subject: [PATCH 08/14] fix(ci): disable writing phpunit cache file --- .github/workflows/test-unit.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index 8fb58cc4f..5c5df9f65 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -76,7 +76,8 @@ jobs: - name: Run PHPUnit tests run: | sudo --preserve-env --user=hab \ - hab pkg exec emergence/phpunit phpunit + hab pkg exec emergence/phpunit phpunit -- \ + --do-not-cache-result - name: Open access to crash log if: always() From 3f1ca930dca93ad7ff597dcad2f7b399773a66ea Mon Sep 17 00:00:00 2001 From: Chris Alfano Date: Mon, 26 Dec 2022 13:40:46 -0500 Subject: [PATCH 09/14] fix(phpunit): only require classes once per file --- phpunit-tests/emergence.read-only/ActiveRecordTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phpunit-tests/emergence.read-only/ActiveRecordTest.php b/phpunit-tests/emergence.read-only/ActiveRecordTest.php index d6a1848ae..b660895f1 100644 --- a/phpunit-tests/emergence.read-only/ActiveRecordTest.php +++ b/phpunit-tests/emergence.read-only/ActiveRecordTest.php @@ -2,14 +2,14 @@ final class ActiveRecordTest extends PHPUnit\Framework\TestCase { - protected function setUp(): void + public static function setUpBeforeClass(): void { require('src/TestRecord.php'); } - public function testFields() + public function testFields(): void { $fields = TestRecord::getClassFields(); - $this->assertEquals(array_keys($fields), array('ID', 'Class', 'Created', 'CreatorID', 'Field1', 'Field2')); + $this->assertEquals(array_keys($fields), array('ID', 'Class', 'Created', 'CreatorID', 'Field1', 'Field2'), 'check class fields list'); } } \ No newline at end of file From eb8f6ac73b2b5cb6e2c82ce27954c2b074ca6f70 Mon Sep 17 00:00:00 2001 From: Chris Alfano Date: Mon, 26 Dec 2022 13:40:55 -0500 Subject: [PATCH 10/14] fix(phpunit): enable printing PHP errors --- phpunit.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/phpunit.xml b/phpunit.xml index 6f98f0d87..689bd601b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,6 +6,8 @@ stopOnError="false"> + + From 3f93633ed0be1e225507fce70446e3b06f2a3889 Mon Sep 17 00:00:00 2001 From: Chris Alfano Date: Mon, 26 Dec 2022 13:41:08 -0500 Subject: [PATCH 11/14] fix(ci): show phpunit testdox output --- .github/workflows/test-unit.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index 5c5df9f65..c1637e3fa 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -77,7 +77,8 @@ jobs: run: | sudo --preserve-env --user=hab \ hab pkg exec emergence/phpunit phpunit -- \ - --do-not-cache-result + --do-not-cache-result \ + --testdox - name: Open access to crash log if: always() From f309cca3a893cfe6883b755482e54de92f29b336 Mon Sep 17 00:00:00 2001 From: Chris Alfano Date: Mon, 26 Dec 2022 13:46:55 -0500 Subject: [PATCH 12/14] test(activerecord): add failing test for nullable default bug --- phpunit-tests/emergence.read-only/ActiveRecordTest.php | 10 +++++++++- phpunit-tests/emergence.read-only/src/TestRecord.php | 7 ++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/phpunit-tests/emergence.read-only/ActiveRecordTest.php b/phpunit-tests/emergence.read-only/ActiveRecordTest.php index b660895f1..7a6342671 100644 --- a/phpunit-tests/emergence.read-only/ActiveRecordTest.php +++ b/phpunit-tests/emergence.read-only/ActiveRecordTest.php @@ -10,6 +10,14 @@ public static function setUpBeforeClass(): void public function testFields(): void { $fields = TestRecord::getClassFields(); - $this->assertEquals(array_keys($fields), array('ID', 'Class', 'Created', 'CreatorID', 'Field1', 'Field2'), 'check class fields list'); + $this->assertEquals(array_keys($fields), array('ID', 'Class', 'Created', 'CreatorID', 'Field1', 'Field2', 'NullableDefault'), 'check class fields list'); + } + + public function testDefaults(): void + { + $Record = new TestRecord(); + $this->assertEquals(1, $Record->NullableDefault, 'unset value returns default'); + $this->assertNull($Record->NullableDefault = null, 'value set to null'); + $this->assertNull($Record->NullableDefault, 'value previously set explicitely to null returns null'); } } \ No newline at end of file diff --git a/phpunit-tests/emergence.read-only/src/TestRecord.php b/phpunit-tests/emergence.read-only/src/TestRecord.php index 6a9a172b0..bbfd88399 100644 --- a/phpunit-tests/emergence.read-only/src/TestRecord.php +++ b/phpunit-tests/emergence.read-only/src/TestRecord.php @@ -4,6 +4,11 @@ class TestRecord extends ActiveRecord { public static $fields = array( 'Field1', - 'Field2' + 'Field2', + 'NullableDefault' => array( + 'type' => 'int', + 'notnull' => false, + 'default' => 1 + ) ); } \ No newline at end of file From ac148132961842d9a0f24c3fe865e764f01171b7 Mon Sep 17 00:00:00 2001 From: Chris Alfano Date: Sun, 18 Dec 2022 02:53:13 -0500 Subject: [PATCH 13/14] fix(ActiveRecord): prevent returning default over null value --- php-classes/ActiveRecord.class.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/php-classes/ActiveRecord.class.php b/php-classes/ActiveRecord.class.php index 12cf15cdd..3275f0741 100644 --- a/php-classes/ActiveRecord.class.php +++ b/php-classes/ActiveRecord.class.php @@ -2227,7 +2227,14 @@ protected function _getFieldValue($field, $useDefault = true) return $value; } } - } elseif ($useDefault && isset($fieldOptions['default'])) { + } elseif ( + $useDefault + && isset($fieldOptions['default']) + && ( + $fieldOptions['notnull'] + || !array_key_exists($fieldOptions['columnName'], $this->_record) + ) + ) { // return default return $fieldOptions['default']; } else { From c2497ec4b7a9ac122b7213eeb35867ca1a5f4c98 Mon Sep 17 00:00:00 2001 From: Chris Alfano Date: Mon, 26 Dec 2022 14:46:08 -0500 Subject: [PATCH 14/14] test(ActiveRecord): add more tests --- .../emergence.read-only/ActiveRecordTest.php | 15 +++++++++++---- .../emergence.read-only/src/TestRecord.php | 5 +++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/phpunit-tests/emergence.read-only/ActiveRecordTest.php b/phpunit-tests/emergence.read-only/ActiveRecordTest.php index 7a6342671..0ff22d8f8 100644 --- a/phpunit-tests/emergence.read-only/ActiveRecordTest.php +++ b/phpunit-tests/emergence.read-only/ActiveRecordTest.php @@ -10,14 +10,21 @@ public static function setUpBeforeClass(): void public function testFields(): void { $fields = TestRecord::getClassFields(); - $this->assertEquals(array_keys($fields), array('ID', 'Class', 'Created', 'CreatorID', 'Field1', 'Field2', 'NullableDefault'), 'check class fields list'); + $this->assertEquals(array_keys($fields), array('ID', 'Class', 'Created', 'CreatorID', 'Field1', 'Field2', 'NullableDefault', 'NotNullableDefault'), 'check class fields list'); } public function testDefaults(): void { $Record = new TestRecord(); - $this->assertEquals(1, $Record->NullableDefault, 'unset value returns default'); - $this->assertNull($Record->NullableDefault = null, 'value set to null'); - $this->assertNull($Record->NullableDefault, 'value previously set explicitely to null returns null'); + + $this->assertEquals(1, $Record->NullableDefault, 'unset nullable value returns default'); + $this->assertNull($Record->NullableDefault = null, 'nullable value set to null'); + $this->assertNull($Record->NullableDefault, 'nullable value previously set explicitely to null returns null'); + + $this->assertEquals(1, $Record->NotNullableDefault, 'unset non-nullable value returns default'); + $this->assertNull($Record->NotNullableDefault = null, 'non-nullable value set to null'); + $this->assertEquals(0, $Record->NotNullableDefault, 'non-nullable value previously set explicitely to null returns null'); + $this->assertEquals(1, $Record->NotNullableDefault = 1, 'non-nullable value set to 1'); + $this->assertEquals(1, $Record->NotNullableDefault, 'non-nullable value previously set explicitely to 1 returns 1'); } } \ No newline at end of file diff --git a/phpunit-tests/emergence.read-only/src/TestRecord.php b/phpunit-tests/emergence.read-only/src/TestRecord.php index bbfd88399..fcd1a092b 100644 --- a/phpunit-tests/emergence.read-only/src/TestRecord.php +++ b/phpunit-tests/emergence.read-only/src/TestRecord.php @@ -9,6 +9,11 @@ class TestRecord extends ActiveRecord 'type' => 'int', 'notnull' => false, 'default' => 1 + ), + 'NotNullableDefault' => array( + 'type' => 'int', + 'notnull' => true, + 'default' => 1 ) ); } \ No newline at end of file