Преглед на файлове

v0.13 - see Changes for detals

Paul Trost преди 10 години
родител
ревизия
7aba380482
променени са 4 файла, в които са добавени 122 реда и са изтрити 100 реда
  1. 4 0
      Changes
  2. 2 0
      Makefile.PL
  3. 63 52
      lib/Disk/SMART.pm
  4. 53 48
      t/01-function_tests.t

+ 4 - 0
Changes

@@ -1,5 +1,9 @@
 Revision history for Disk-SMART
 
+0.13	2015-06-11
+	Removed bad mocking code from module and unit tests and refactored to use Test::MockModule
+	Refactored getting smart output into subroutine
+
 0.12	2015-05-02
 	Added get_disk_list() to detect sdX and hdX devices
 	Changed new() to use get_disk_list() if no device list is passed to it

+ 2 - 0
Makefile.PL

@@ -15,6 +15,7 @@ WriteMakefile(
         'Test::Fatal'         => 0,
         'Test::Pod'           => 0,
         'Test::Pod::Coverage' => 0,
+        'Test::MockModule'    => 0,
     },
     PREREQ_PM => {
         'Math::Round' => 0,
@@ -47,6 +48,7 @@ WriteMakefile(
                     'Test::Fatal'         => 0,
                     'Test::Pod'           => 0,
                     'Test::Pod::Coverage' => 0,
+                    'Test::MockModule'    => 0,
                 }
             }
         }

+ 63 - 52
lib/Disk/SMART.pm

@@ -2,12 +2,13 @@ package Disk::SMART;
 
 use warnings;
 use strict;
+use 5.010;
 use Carp;
 use Math::Round;
 use File::Which;
 
 {
-    $Disk::SMART::VERSION = '0.12'
+    $Disk::SMART::VERSION = '0.13'
 }
 
 our $smartctl = which('smartctl');
@@ -46,14 +47,14 @@ Returns C<Disk::SMART> object if smartctl is available and can poll the given de
 =cut
 
 sub new {
-    my ( $class, @p_devices ) = @_;
+    my ( $class, @devices ) = @_;
     my $self = bless {}, $class;
-    my @devices = @p_devices ? @p_devices : $self->get_disk_list();
+    @devices = @devices ? @devices : $self->get_disk_list();
 
     croak "Valid device identifier not supplied to constructor for $class.\n"
-        if !@devices && !defined $ENV{'MOCK_TEST_DATA'};
+        if !@devices;
     croak "smartctl binary was not found on your system, are you running as root?\n"
-        if !-f $smartctl && !defined $ENV{'MOCK_TEST_DATA'};
+        if !-f $smartctl;
 
     $self->update_data(@devices);
 
@@ -144,6 +145,22 @@ sub get_disk_health {
 }
 
 
+=head2 B<get_disk_list>
+
+Returns list of detected hda and sda devices. This method can be called manually if unsure what devices are present. 
+
+    $smart->get_disk_list;
+
+=cut
+    
+sub get_disk_list {
+    open my $fh, '-|', 'parted -l' or confess "Can't run parted binary\n";
+    local $/ = undef;
+    my @disks = map { /Disk (\/.*\/[h|s]d[a-z]):/ } split /\n/, <$fh>;
+    close $fh or confess "Can't close file handle reading parted output\n";
+    return @disks;
+}
+
 =head2 B<get_disk_model(DEVICE)>
 
 Returns the model of the device. eg. "ST3250410AS".
@@ -161,7 +178,6 @@ sub get_disk_model {
     return $self->{'devices'}->{$device}->{'model'};
 }
 
-
 =head2 B<get_disk_temp(DEVICE)>
 
 Returns an array with the temperature of the device in Celsius and Farenheit, or N/A.
@@ -179,6 +195,33 @@ sub get_disk_temp {
     return @{ $self->{'devices'}->{$device}->{'temp'} };
 }
 
+=head2 B<run_short_test(DEVICE)>
+
+Runs the SMART short self test and returns the result.
+
+C<DEVICE> - Device identifier of SSD/ Hard Drive
+
+    $smart->run_short_test('/dev/sda');
+
+=cut
+
+sub run_short_test {
+    my ( $self, $device ) = @_;
+    $self->_validate_param($device);
+
+    my $test_out = get_smart_output( $device, '-t short' );
+    my ($short_test_time) = $test_out =~ /Please wait (.*) minutes/s;
+    sleep( $short_test_time * 60 );
+
+    my $smart_output = _get_smart_output( $device, '-a' );
+    ($smart_output) = $smart_output =~ /(SMART Self-test log.*)\nSMART Selective self-test/s;
+    my @device_tests      = split /\n/, $smart_output;
+    my $short_test_number = $device_tests[2];
+    my $short_test_status = substr $short_test_number, 25, +30;
+    $short_test_status = _trim($short_test_status);
+
+    return $short_test_status;
+}
 
 =head2 B<update_data(DEVICE)>
 
@@ -196,10 +239,8 @@ sub update_data {
 
     foreach my $device (@devices) {
         my $out;
-        $out = $ENV{'MOCK_TEST_DATA'} if defined $ENV{'MOCK_TEST_DATA'};
-        $out = qx($smartctl -a $device -d sat)
-          if ( !defined $ENV{'MOCK_TEST_DATA'} && -f $smartctl );
-        confess "Smartctl couldn't poll device $device\n"
+        $out = _get_smart_output( $device, '-a' );
+        confess "Smartctl couldn't poll device $device\nSmartctl Output:\n$out\n"
           if ( !$out || $out !~ /START OF INFORMATION SECTION/ );
 
         chomp($out);
@@ -215,49 +256,19 @@ sub update_data {
     return;
 }
 
-=head2 B<run_short_test(DEVICE)>
-
-Runs the SMART short self test and returns the result.
-
-C<DEVICE> - Device identifier of SSD/ Hard Drive
-
-    $smart->run_short_test('/dev/sda');
-
-=cut
-
-sub run_short_test {
-    my ( $self, $device ) = @_;
-    $self->_validate_param($device);
-
-    if ( !defined $ENV{'MOCK_TEST_DATA'} ) {
-        my $out = qx( $smartctl -t short $device );
-        my ($short_test_time) = $out =~ /Please wait (.*) minutes/s;
-        sleep( $short_test_time * 60 );
-    }
-
-    my $smart_output = $ENV{'MOCK_TEST_DATA'} // qx($smartctl -a $device);
-    ($smart_output) = $smart_output =~ /(SMART Self-test log.*)\nSMART Selective self-test/s;
-    my @device_tests      = split /\n/, $smart_output;
-    my $short_test_number = $device_tests[2];
-    my $short_test_status = substr $short_test_number, 25, +30;
-    $short_test_status = _trim($short_test_status);
-
-    return $short_test_status;
-}
-
-=head2 B<get_disk_list>
-
-Returns list of detected hda and sda devices. This method can be called manually if unsure what devices are present. 
-
-    $smart->get_disk_list;
-
-=cut
-
-sub get_disk_list {
-    open my $raw_out, '-|', 'parted -l';
+sub _get_smart_output {
+    my ( $device, $options ) = @_;
+    $options = $options // '';
+    open my $fh, '-|', "$smartctl $device $options" or confess "Can't run smartctl binary\n";
     local $/ = undef;
-    my @disks = map { /Disk (\/.*\/[h|s]d[a-z]):/ } split /\n/, <$raw_out>;
-    return @disks;
+    my $smart_output = <$fh>;
+    #close $fh or confess "Can't close file handle reading smartctl output\n";
+    if ( $smart_output =~ /Unknown USB bridge/ ) {
+        open $fh, '-|', "$smartctl $device $options -d sat" or confess "Can't run smartctl binary\n";
+        $smart_output = <$fh>;
+        close $fh or confess "Can't close file handle reading smartctl output\n";
+    }
+    return $smart_output;
 }
 
 sub _process_disk_attributes {

+ 53 - 48
t/01-function_tests.t

@@ -1,13 +1,10 @@
-use warnings;
-use strict;
-use Test::More 'tests' => 18;
 use Test::Fatal;
+use Test::More tests => 17;
+use Test::MockModule;
 use Disk::SMART;
 
-
-$ENV{'MOCK_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
+my $smart_output = "smartctl 6.2 2013-07-26 r3841 [x86_64-linux-3.10.0-229.4.2.el7.x86_64] (local build)
+Copyright (C) 2002-13, Bruce Allen, Christian Franke, www.smartmontools.org
 
 === START OF INFORMATION SECTION ===
 Model Family:     Seagate Barracuda 7200.10
@@ -17,9 +14,8 @@ 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:    Wed Oct 15 17:16:35 2014 CDT
+ATA Version is:   ATA/ATAPI-7 (minor revision not indicated)
+Local Time is:    Thu May 28 10:38:55 2015 CDT
 SMART support is: Available - device has SMART capability.
 SMART support is: Enabled
 
@@ -31,9 +27,9 @@ 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 
+                    without error or no self-test has ever
                     been run.
-Total time to complete Offline 
+Total time to complete Offline
 data collection:        (  430) seconds.
 Offline data collection
 capabilities:            (0x5b) SMART execute Offline immediate.
@@ -49,7 +45,7 @@ SMART capabilities:            (0x0003) Saves SMART data before entering
                     Supports SMART auto save timer.
 Error logging capability:        (0x01) Error logging supported.
                     General Purpose Logging supported.
-Short self-test routine 
+Short self-test routine
 recommended polling time:    (   1) minutes.
 Extended self-test routine
 recommended polling time:    (  64) minutes.
@@ -59,18 +55,18 @@ 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
+  3 Spin_Up_Time            0x0003   097   097   000    Pre-fail  Always       -       0
+  4 Start_Stop_Count        0x0032   100   100   020    Old_age   Always       -       33
   5 Reallocated_Sector_Ct   0x0033   100   100   036    Pre-fail  Always       -       0
-  7 Seek_Error_Rate         0x000f   070   060   030    Pre-fail  Always       -       10571330
-  9 Power_On_Hours          0x0032   099   099   000    Old_age   Always       -       1100
+  7 Seek_Error_Rate         0x000f   075   060   030    Pre-fail  Always       -       40666623
+  9 Power_On_Hours          0x0032   093   093   000    Old_age   Always       -       6356
  10 Spin_Retry_Count        0x0013   100   100   097    Pre-fail  Always       -       0
- 12 Power_Cycle_Count       0x0032   100   100   020    Old_age   Always       -       19
+ 12 Power_Cycle_Count       0x0032   100   100   020    Old_age   Always       -       33
 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   065   050   045    Old_age   Always       -       35 (Min/Max 13/50)
-194 Temperature_Celsius     0x0022   035   050   000    Old_age   Always       -       35 (0 13 0 0)
-195 Hardware_ECC_Recovered  0x001a   066   060   000    Old_age   Always       -       59046455
+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 0)
+195 Hardware_ECC_Recovered  0x001a   069   060   000    Old_age   Always       -       121831984
 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
@@ -96,40 +92,49 @@ SMART Selective self-test log data structure revision number 1
     3        0        0  Not_testing
     4        0        0  Not_testing
     5        0        0  Not_testing
-Selective self-test flags (0x0):
+elsius
   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.';
-
-my $disk  = '/dev/test_good';
-isa_ok ( Disk::SMART->new($disk), 'Disk::SMART', 'Constructor returns new object' );
-
+If Selective self-test is pending on power-up, resume after 0 minute delay.";
+
+my $mock  = Test::MockModule->new('Disk::SMART');
+$mock->mock(
+    '_get_smart_output' => sub { return $smart_output; },
+    'run_short_test'    => 'Completed without error'
+);
+my $disk  = '/dev/sda';
 my $smart = Disk::SMART->new($disk);
-#Positive testing
-is( $smart->get_disk_temp($disk), 2, 'get_disk_temp() returns device temperature' );
+            
+# Verify functions return correctly with SMART data present
+my %disk_attribs = $smart->get_disk_attributes($disk);
+is( keys %disk_attribs, 18, 'get_disk_attributes() returns hash of device attributes' );
+is( $smart->get_disk_errors($disk), 'No Errors Logged', 'get_disk_errors() returns no errors logged' );
 is( $smart->get_disk_health($disk), 'PASSED', 'get_disk_health() returns health status' );
 is( $smart->get_disk_model($disk), 'ST3250410AS', 'get_disk_model() returns device model' );
-is( $smart->get_disk_errors($disk), 'No Errors Logged', 'get_disk_errors() returns proper string' );
-
-my %attribs = $smart->get_disk_attributes($disk);
-is( keys %attribs, 18, 'get_disk_attributes() returns hash of device attributes' );
-is( $smart->run_short_test($disk), 'Completed without error', 'run_short_test() returns proper string' );
-
-$ENV{'MOCK_TEST_DATA'} =~ s/ST3250410AS//;
-$ENV{'MOCK_TEST_DATA'} =~ s/187 Reported_Uncorrect      0x0032   100   100   000    Old_age   Always       -       0/187 Reported_Uncorrect      0x0032   100   100   000    Old_age   Always       -       1/;
+is( scalar($smart->get_disk_temp($disk)), 2, 'get_disk_temp() returns device temperature' );
+is( $smart->run_short_test($disk), 'Completed without error', 'run_short_test() returns test status' );
+
+# Change some SMART data around and see if the functions return correctly
+$smart_output =~ s/ST3250410AS//;
+$smart_output =~ s/187 Reported_Uncorrect      0x0032   100   100   000    Old_age   Always       -       0/187 Reported_Uncorrect      0x0032   100   100   000    Old_age   Always       -       1/;
+$smart_output =~ s/190 Airflow_Temperature_Cel.*\n//;
+$smart_output =~ s/194 Temperature_Celsius.*\n//;
 is( $smart->update_data($disk), undef, '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' );
-is( $smart->get_disk_temp($disk), 2, 'get_disk_temp() returns device temperature' );
 is( $smart->get_disk_health($disk), 'PASSED: 187 - Reported_Uncorrect = 1', 'get_disk_health() returns failed attribute status when SMART attribute 187 > 0' );
+is( $smart->get_disk_model($disk), 'N/A', 'get_disk_model() returns N/A when no model was found' );
+my @disk_temps = $smart->get_disk_temp($disk);
+is( $disk_temps[0], 'N/A', "get_disk_temp() returns 'N/A' when smartctl doesn't report temperaure" );
 
-#Negative testing
+#Exception testing
 $disk  = '/dev/test_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' );
+$mock->mock( 
+    'update_data'       => "Smartctl couldn't poll device",
+);
+$mock->unmock('run_short_test');
+
 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->get_disk_errors($disk); },     qr/$disk not found in object/, 'get_disk_model() 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_model($disk); },      qr/$disk not found in object/, 'get_disk_model() returns failure when passed invalid device' );
+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->run_short_test($disk); },      qr/$disk not found in object/, 'run_short_test() returns failure when passed invalid device' );
-
-$ENV{'MOCK_TEST_DATA'} = undef;
-like( exception { $smart->update_data($disk); }, qr/couldn't poll/, 'update_data() returns falure when passed invalid device' );
-
+like( $smart->update_data($disk),                        qr/couldn't poll/, 'update_data() returns falure when passed invalid device' );