Browse Source

Code cleanup, implementation of Test::Fatal, implementation of dummy testing data

Paul Trost 11 years ago
parent
commit
10f33c60ec
5 changed files with 183 additions and 71 deletions
  1. 7 1
      Changes
  2. 4 2
      Makefile.PL
  3. 60 52
      lib/Disk/SMART.pm
  4. 1 1
      t/01-instantiation.t
  5. 111 15
      t/02-get_functions.t

+ 7 - 1
Changes

@@ -1,5 +1,11 @@
 Revision history for Disk-SMART
 Revision history for Disk-SMART
 
 
+        
+
+0.07    2014-10-13
+        Implemented dummy testing in unit tests to bypass the need for smokers having smartctl installed
+        Regex and function cleanups
+
 0.06    2014-10-09
 0.06    2014-10-09
         Fixed issue with N/A disk temp not returning correctly and causing failure
         Fixed issue with N/A disk temp not returning correctly and causing failure
 
 
@@ -22,7 +28,7 @@ Revision history for Disk-SMART
         Added get_disk_errors()
         Added get_disk_errors()
         Methods now validate the device passed to it, to ensure smartctl has already read it
         Methods now validate the device passed to it, to ensure smartctl has already read it
         Added unit tests to test passing invalid device to methods
         Added unit tests to test passing invalid device to methods
-        Changed to croak() as I didn't feel a trace was necessary with proper error returning.
+        Changed to croak() as I didn't feel a trace was necessary with proper error returning
 
 
 0.02    2014-10-03
 0.02    2014-10-03
         Added confess to new() in case smartctl cannot find the specified device
         Added confess to new() in case smartctl cannot find the specified device

+ 4 - 2
Makefile.PL

@@ -11,7 +11,8 @@ WriteMakefile(
     ABSTRACT_FROM    => 'lib/Disk/SMART.pm',
     ABSTRACT_FROM    => 'lib/Disk/SMART.pm',
     PL_FILES         => {},
     PL_FILES         => {},
     TEST_REQUIRES    => {
     TEST_REQUIRES    => {
-        'Test::More' => 0,
+        'Test::More'  => 0,
+        'Test::Fatal' => 0,
     },
     },
     PREREQ_PM => {
     PREREQ_PM => {
         'Math::Round' => 0,
         'Math::Round' => 0,
@@ -39,7 +40,8 @@ WriteMakefile(
             },
             },
             test => {
             test => {
                 requires => {
                 requires => {
-                    'Test::More' => 0,
+                    'Test::More'  => 0,
+                    'Test::Fatal' => 0,
                 }
                 }
             }
             }
         }
         }

+ 60 - 52
lib/Disk/SMART.pm

@@ -18,7 +18,6 @@ Disk::SMART - Provides an interface to smartctl
 =head1 SYNOPSIS
 =head1 SYNOPSIS
 
 
 Disk::SMART is an object ooriented module that provides an interface to get SMART disk info from a device as well as initiate testing.
 Disk::SMART is an object ooriented module that provides an interface to get SMART disk info from a device as well as initiate testing.
-
     use Disk::SMART;
     use Disk::SMART;
 
 
     my $smart = Disk::SMART->new('/dev/sda');
     my $smart = Disk::SMART->new('/dev/sda');
@@ -32,31 +31,38 @@ Disk::SMART is an object ooriented module that provides an interface to get SMAR
 
 
 Instantiates the Disk::SMART object
 Instantiates the Disk::SMART object
 
 
-C<DEVICE> - Device identifier of SSD / Hard Drive
+C<DEVICE> - Device identifier of SSD / Hard Drive. The constructor takes either a single device name, or an array of device names.
 
 
     my $smart = Disk::SMART->new( 'dev/sda', '/dev/sdb' );
     my $smart = Disk::SMART->new( 'dev/sda', '/dev/sdb' );
 
 
-Returns C<Disk::SMART> object if smartctl is available and can poll the given device.
+Returns C<Disk::SMART> object if smartctl is available and can poll the given device(s).
 
 
 =cut
 =cut
 
 
 sub new {
 sub new {
     my ( $class, @devices ) = @_;
     my ( $class, @devices ) = @_;
     my $self = bless {}, $class;
     my $self = bless {}, $class;
+    my $test_data;
 
 
-    croak "Valid device identifier not supplied to constructor for $class.\n" if ( !@devices );
-    croak "smartctl binary was not found on your system, are you running as root?\n" if !-f $smartctl;
+    if ( $ENV{'TEST_MOCK_DATA'} ) {
+        $test_data = pop @devices;
+    }
+    
+    croak "Valid device identifier not supplied to constructor for $class.\n"
+        if !@devices && !$ENV{'TEST_MOCK_DATA'};
+    croak "smartctl binary was not found on your system, are you running as root?\n"
+        if !-f $smartctl && !$ENV{'TEST_MOCK_DATA'};
 
 
     foreach my $device (@devices) {
     foreach my $device (@devices) {
-        $self->update_data($device);
+      ($test_data) ? $self->update_data($device, $test_data) : $self->update_data($device);
     }
     }
+
     return $self;
     return $self;
 }
 }
 
 
 
 
 =head1 USER METHODS
 =head1 USER METHODS
 
 
-
 =head2 B<get_disk_attributes(DEVICE)>
 =head2 B<get_disk_attributes(DEVICE)>
 
 
 Returns hash of the SMART disk attributes and values
 Returns hash of the SMART disk attributes and values
@@ -69,6 +75,7 @@ C<DEVICE> - Device identifier of SSD/ Hard Drive
 
 
 sub get_disk_attributes {
 sub get_disk_attributes {
     my ( $self, $device ) = @_;
     my ( $self, $device ) = @_;
+    $self->_validate_param($device);
     return $self->{'devices'}->{$device}->{'attributes'};
     return $self->{'devices'}->{$device}->{'attributes'};
 }
 }
 
 
@@ -85,6 +92,7 @@ C<DEVICE> - Device identifier of SSD/ Hard Drive
 
 
 sub get_disk_errors {
 sub get_disk_errors {
     my ( $self, $device ) = @_;
     my ( $self, $device ) = @_;
+    $self->_validate_param($device);
     return $self->{'devices'}->{$device}->{'errors'};
     return $self->{'devices'}->{$device}->{'errors'};
 }
 }
 
 
@@ -101,6 +109,7 @@ C<DEVICE> - Device identifier of SSD / Hard Drive
 
 
 sub get_disk_health {
 sub get_disk_health {
     my ( $self, $device ) = @_;
     my ( $self, $device ) = @_;
+    $self->_validate_param($device);
     return $self->{'devices'}->{$device}->{'health'};
     return $self->{'devices'}->{$device}->{'health'};
 }
 }
 
 
@@ -117,6 +126,7 @@ C<DEVICE> - Device identifier of SSD / Hard Drive
 
 
 sub get_disk_model {
 sub get_disk_model {
     my ( $self, $device ) = @_;
     my ( $self, $device ) = @_;
+    $self->_validate_param($device);
     return $self->{'devices'}->{$device}->{'model'};
     return $self->{'devices'}->{$device}->{'model'};
 }
 }
 
 
@@ -133,6 +143,7 @@ C<DEVICE> - Device identifier of SSD / Hard Drive
 
 
 sub get_disk_temp {
 sub get_disk_temp {
     my ( $self, $device ) = @_;
     my ( $self, $device ) = @_;
+    $self->_validate_param($device);
     return @{ $self->{'devices'}->{$device}->{'temp'} };
     return @{ $self->{'devices'}->{$device}->{'temp'} };
 }
 }
 
 
@@ -148,16 +159,24 @@ C<DEVICE> - Device identifier of SSD/ Hard Drive
 =cut
 =cut
 
 
 sub update_data {
 sub update_data {
-    my ( $self, $device ) = @_;
+    my ( $self, $device, $test_data ) = @_;
+    my $out = $test_data // undef;
+
+    $out = qx($smartctl -a $device) if !defined $test_data;
+    my $retval = $?;
+
+    if ( !$test_data ) {
+        croak "Smartctl couldn't poll device $device\n"
+            if ( $out !~ /START OF INFORMATION SECTION/ );
+    }
 
 
-    chomp( my $out = qx($smartctl -a $device) );
-    croak "Smartctl couldn't poll device $device\n" if $out =~ /No such device/;
+    chomp($out);
     $self->{'devices'}->{$device}->{'SMART_OUTPUT'} = $out;
     $self->{'devices'}->{$device}->{'SMART_OUTPUT'} = $out;
     
     
     # update_data() can be called at any time with a device name. Let's check
     # update_data() can be called at any time with a device name. Let's check
     # the device name given to make sure it matches what was given during
     # the device name given to make sure it matches what was given during
     # object construction.
     # object construction.
-    croak "$device not found in object, You probably didn't enter it right" if ( !exists $self->{'devices'}->{$device} );
+    $self->_validate_param($device);
 
 
     $self->_process_disk_attributes($device);
     $self->_process_disk_attributes($device);
     $self->_process_disk_errors($device);
     $self->_process_disk_errors($device);
@@ -183,6 +202,7 @@ sub _process_disk_attributes {
         $value =~ s/^\s+//g;    # trim beginning and ending whitepace
         $value =~ s/^\s+//g;    # trim beginning and ending whitepace
         $self->{'devices'}->{$device}->{'attributes'}->{$name} = $value;
         $self->{'devices'}->{$device}->{'attributes'}->{$name} = $value;
     }
     }
+
     return;
     return;
 }
 }
 
 
@@ -192,74 +212,62 @@ sub _process_disk_errors {
 
 
     my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
     my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
     my ($errors) = $smart_output =~ /SMART Error Log Version: [1-9](.*)SMART Self-test log/s;
     my ($errors) = $smart_output =~ /SMART Error Log Version: [1-9](.*)SMART Self-test log/s;
-
-    if ( !defined $errors ) {
-        $self->{'devices'}->{$device}->{'errors'} = 'N/A';
-        return;
-    }
-
     $errors =~ s/^\s+|\s+$//g;    #trim beginning and ending whitepace
     $errors =~ s/^\s+|\s+$//g;    #trim beginning and ending whitepace
-    $self->{'devices'}->{$device}->{'errors'} = $errors;
-    return;
+    $errors = 'N/A' if !$errors;
+
+    return $self->{'devices'}->{$device}->{'errors'} = $errors;
 }
 }
 
 
 sub _process_disk_health {
 sub _process_disk_health {
     my ( $self, $device ) = @_;
     my ( $self, $device ) = @_;
     $self->_validate_param($device);
     $self->_validate_param($device);
-    my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
-    my ($health) = $smart_output =~ /(SMART overall-health self-assessment.*\n)/;
 
 
-    if ( (!defined $health) or $health !~ /PASSED|FAILED/x ) {
-        $self->{'devices'}->{$device}->{'health'} = 'N/A';
-        return;
-    }
+    my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
+    my ($health) = $smart_output =~ /SMART overall-health self-assessment test result:(.*)\n/;
+    $health =~ s/^\s+|\s+$//g;    #trim beginning and ending whitepace
+    $health = 'N/A' if !$health or $health !~ /PASSED|FAILED/x;
 
 
-    $health =~ s/.*: //;
-    chomp $health;
-    $self->{'devices'}->{$device}->{'health'} = $health;
-    return;
+    return $self->{'devices'}->{$device}->{'health'} = $health;
 }
 }
 
 
 sub _process_disk_model {
 sub _process_disk_model {
     my ( $self, $device ) = @_;
     my ( $self, $device ) = @_;
     $self->_validate_param($device);
     $self->_validate_param($device);
+    
     my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
     my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
-    my ($model) = $smart_output =~ /(Device\ Model.*\n)/;
-
-    if ( !defined $model ) {
-        $self->{'devices'}->{$device}->{'model'} = 'N/A';
-        return;
-    }
-
-    $model =~ s/.*:\ //;
+    my ($model) = $smart_output =~ /Device\ Model:(.*)\n/;
     $model =~ s/^\s+|\s+$//g;    #trim beginning and ending whitepace
     $model =~ s/^\s+|\s+$//g;    #trim beginning and ending whitepace
-    $self->{'devices'}->{$device}->{'model'} = $model;
-    return;
+    $model = 'N/A' if !$model;
+
+    return $self->{'devices'}->{$device}->{'model'} = $model;
 }
 }
 
 
 sub _process_disk_temp {
 sub _process_disk_temp {
     my ( $self, $device ) = @_;
     my ( $self, $device ) = @_;
     $self->_validate_param($device);
     $self->_validate_param($device);
-    my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
-    my ($temp_c) = $smart_output =~ /(Temperature_Celsius.*\n)/;
+    my ( $temp_c, $temp_f );
 
 
-    if ( !defined $temp_c || $smart_output =~ qr/S.M.A.R.T. not available/x ) {
-        $self->{'devices'}->{$device}->{'temp'} = [( 'N/A' )];
-        return;
+    my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
+    ($temp_c) = $smart_output =~ /(Temperature_Celsius.*\n)/;
+
+    if ($temp_c) {
+        $temp_c = substr $temp_c, 83, +3;
+        $temp_c =~ s/^\s+|\s+$//g;    #trim beginning and ending whitepace
+        $temp_f = round( ( $temp_c * 9 ) / 5 + 32 );
+        $temp_c = int $temp_c;
+        $temp_f = int $temp_f;
+    }
+    else {
+        $temp_c = 'N/A';
+        $temp_f = 'N/A';
     }
     }
 
 
-    chomp($temp_c);
-    $temp_c = substr $temp_c, 83, +3;
-    $temp_c =~ s/ //g;
-
-    my $temp_f = round( ( $temp_c * 9 ) / 5 + 32 );
-    $self->{'devices'}->{$device}->{'temp'} = [ ( int $temp_c, int $temp_f ) ];
-    return;
+    return $self->{'devices'}->{$device}->{'temp'} = [ ( $temp_c, $temp_f ) ];
 }
 }
 
 
 sub _validate_param {
 sub _validate_param {
     my ( $self, $device ) = @_;
     my ( $self, $device ) = @_;
-    croak "$device not found in object, You probably didn't enter it right" if ( !exists $self->{'devices'}->{$device} );
+    croak "$device not found in object, you probably didn't enter it right" if ( !exists $self->{'devices'}->{$device} );
     return;
     return;
 }
 }
     
     

+ 1 - 1
t/01-instantiation.t

@@ -5,4 +5,4 @@ use Disk::SMART;
 
 
 my $smart = Disk::SMART->new('/dev/sda');
 my $smart = Disk::SMART->new('/dev/sda');
 
 
-pass('instantiation of object successful') if (defined($smart));
+pass('instantiation of object successful') if ( defined($smart) );

+ 111 - 15
t/02-get_functions.t

@@ -1,26 +1,122 @@
 use warnings;
 use warnings;
 use strict;
 use strict;
-use Test::More 'tests' => 12;
+use Test::More 'tests' => 13;
+use Test::Fatal;
 use Disk::SMART;
 use Disk::SMART;
+use Data::Dumper;
 
 
-my $disk  = '/dev/sda';
-my $smart = Disk::SMART->new($disk);
+my $test_data = 
+'smartctl 5.41 2011-06-09 r3365 [x86_64-linux-2.6.32-32-pve] (local build)
+Copyright (C) 2002-11 by Bruce Allen, http://smartmontools.sourceforge.net
 
 
-ok( $smart->get_disk_temp($disk), 'get_disk_temp() returns drive temperature or N/A' );
-is( eval{$smart->get_disk_temp('/dev/sdz')}, undef, 'get_disk_temp() returns failure when passed invalid device' );
+=== START OF INFORMATION SECTION ===
+Model Family:     Seagate Barracuda 7200.10
+Device Model:     ST3250410AS
+Serial Number:    6RYBDDDQ
+Firmware Version: 3.AAF
+User Capacity:    250,059,350,016 bytes [250 GB]
+Sector Size:      512 bytes logical/physical
+Device is:        In smartctl database [for details use: -P show]
+ATA Version is:   7
+ATA Standard is:  Exact ATA specification draft version not indicated
+Local Time is:    Thu Oct  9 17:36:15 2014 CDT
+SMART support is: Available - device has SMART capability.
+SMART support is: Enabled
 
 
-like( $smart->get_disk_health($disk), qr/PASSED|FAILED|N\/A/, 'get_disk_health() returns health status or N/A' );
-is( eval{$smart->get_disk_health('/dev/sdz')}, undef, 'get_disk_health() returns failure when passed invalid device' );
+=== START OF READ SMART DATA SECTION ===
+SMART overall-health self-assessment test result: PASSED
 
 
-cmp_ok( length $smart->get_disk_model($disk), '>=', 3, 'get_disk_model() returns disk model or N/A' );
-is( eval{$smart->get_disk_errors('/dev/sdz')}, undef, 'get_disk_model() returns failure when passed invalid device' );
+General SMART Values:
+Offline data collection status:  (0x82) Offline data collection activity
+                                        was completed without error.
+                                        Auto Offline Data Collection: Enabled.
+Self-test execution status:      (   0) The previous self-test routine completed
+                                        without error or no self-test has ever
+                                        been run.
+Total time to complete Offline
+data collection:                (  430) seconds.
+Offline data collection
+capabilities:                    (0x5b) SMART execute Offline immediate.
+                                        Auto Offline data collection on/off support.
+                                        Suspend Offline collection upon new
+                                        command.
+                                        Offline surface scan supported.
+                                        Self-test supported.
+                                        No Conveyance Self-test supported.
+                                        Selective Self-test supported.
+SMART capabilities:            (0x0003) Saves SMART data before entering
+                                        power-saving mode.
+                                        Supports SMART auto save timer.
+Error logging capability:        (0x01) Error logging supported.
+                                        General Purpose Logging supported.
+Short self-test routine
+recommended polling time:        (   1) minutes.
+Extended self-test routine
+recommended polling time:        (  64) minutes.
+SCT capabilities:              (0x0001) SCT Status supported.
 
 
-like( $smart->get_disk_errors($disk), qr/\w+/, 'get_disk_errors() returns proper string' );
-is( eval{$smart->get_disk_errors('/dev/sdz')}, undef, 'get_disk_errors() returns failure when passed invalid device' );
+SMART Attributes Data Structure revision number: 10
+Vendor Specific SMART Attributes with Thresholds:
+ID# ATTRIBUTE_NAME          FLAG     VALUE WORST THRESH TYPE      UPDATED  WHEN_FAILED RAW_VALUE
+  1 Raw_Read_Error_Rate     0x000f   100   253   006    Pre-fail  Always       -       0
+  3 Spin_Up_Time            0x0003   098   098   000    Pre-fail  Always       -       0
+  4 Start_Stop_Count        0x0032   100   100   020    Old_age   Always       -       19
+  5 Reallocated_Sector_Ct   0x0033   100   100   036    Pre-fail  Always       -       0
+  7 Seek_Error_Rate         0x000f   069   060   030    Pre-fail  Always       -       10295374
+  9 Power_On_Hours          0x0032   099   099   000    Old_age   Always       -       957
+ 10 Spin_Retry_Count        0x0013   100   100   097    Pre-fail  Always       -       0
+ 12 Power_Cycle_Count       0x0032   100   100   020    Old_age   Always       -       19
+187 Reported_Uncorrect      0x0032   100   100   000    Old_age   Always       -       0
+189 High_Fly_Writes         0x003a   100   100   000    Old_age   Always       -       0
+190 Airflow_Temperature_Cel 0x0022   064   050   045    Old_age   Always       -       36 (Min/Max 13/50)
+194 Temperature_Celsius     0x0022   036   050   000    Old_age   Always       -       36 (0 13 0 0)
+195 Hardware_ECC_Recovered  0x001a   067   060   000    Old_age   Always       -       214399999
+197 Current_Pending_Sector  0x0012   100   100   000    Old_age   Always       -       0
+198 Offline_Uncorrectable   0x0010   100   100   000    Old_age   Offline      -       0
+199 UDMA_CRC_Error_Count    0x003e   200   200   000    Old_age   Always       -       0
+200 Multi_Zone_Error_Rate   0x0000   100   253   000    Old_age   Offline      -       0
+202 Data_Address_Mark_Errs  0x0032   100   253   000    Old_age   Always       -       0
 
 
-cmp_ok( scalar( keys $smart->get_disk_attributes($disk) ), '>', 1, 'get_disk_attributes() returns hash of device attributes' );
-is( eval{$smart->get_disk_attributes('/dev/sdz')}, undef, 'get_disk_attributes() returns failure when passed invalid device' );
+SMART Error Log Version: 1
+No Errors Logged
 
 
-is( $smart->update_data($disk), 1, 'update_data() updated object with current device data' );
-is( eval{$smart->update_data('/dev/sdz')}, undef, 'update_data() returns falure when passed invalid device' );
+SMART Self-test log structure revision number 1
+
+SMART Selective self-test log data structure revision number 1
+ SPAN  MIN_LBA  MAX_LBA  CURRENT_TEST_STATUS
+    1        0        0  Not_testing
+    2        0        0  Not_testing
+    3        0        0  Not_testing
+    4        0        0  Not_testing
+    5        0        0  Not_testing
+Selective self-test flags (0x0):
+  After scanning selected spans, do NOT read-scan remainder of disk.
+If Selective self-test is pending on power-up, resume after 0 minute delay.';
+
+
+$ENV{'TEST_MOCK_DATA'} = 1;
+
+my $disk  = '/dev/blah_good';
+my $smart = Disk::SMART->new( $disk, $test_data );
+
+#Positive testing
+is( $smart->get_disk_temp($disk), 2, 'get_disk_temp() returns drive temperature' );
+is( $smart->get_disk_health($disk), 'PASSED', 'get_disk_health() returns health status' );
+is( $smart->get_disk_model($disk), 'ST3250410AS', 'get_disk_model() returns disk model or N/A' );
+is( $smart->get_disk_errors($disk), 'No Errors Logged', 'get_disk_errors() returns proper string' );
+is( scalar( keys $smart->get_disk_attributes($disk) ), 18, 'get_disk_attributes() returns hash of device attributes' );
+
+my $new_test_data = $test_data;
+$new_test_data =~ s/ST3250410AS//;
+is( $smart->update_data( $disk, $new_test_data ), 1, 'update_data() updated object with changed device data' );
+is( $smart->get_disk_model($disk), 'N/A', 'get_disk_model() returns N/A with changed device data' );
+
+#Negative testing
+$disk  = '/dev/blah_bad';
+like( exception { $smart->get_disk_temp($disk); },       qr/$disk not found in object/, 'get_disk_temp() returns failure when passed invalid device' );
+like( exception { $smart->get_disk_health($disk); },     qr/$disk not found in object/, 'get_disk_health() returns failure when passed invalid device' );
+like( exception { $smart->get_disk_errors($disk); },     qr/$disk not found in object/, 'get_disk_model() returns failure when passed invalid device' );
+like( exception { $smart->get_disk_errors($disk); },     qr/$disk not found in object/, 'get_disk_errors() returns failure when passed invalid device' );
+like( exception { $smart->get_disk_attributes($disk); }, qr/$disk not found in object/, 'get_disk_attributes() returns failure when passed invalid device' );
+like( exception { $smart->update_data($disk); },         qr/couldn't poll/,             'update_data() returns falure when passed invalid device' );