From 0937bf728caed9503e2b5aae306036f9bb68e088 Mon Sep 17 00:00:00 2001 From: Fabian Zahn <fabian.zahn@gmail.com> Date: Thu, 22 Feb 2018 19:55:40 +0100 Subject: [PATCH 1/5] - Removed member variable @test_flag - Fixed stdout output if fixture is active - Refactored the state manipulation of @test_suite and moved it completely into test_suite_verify() --- auto/parse_output.rb | 61 ++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/auto/parse_output.rb b/auto/parse_output.rb index b5132a0..33d1f71 100644 --- a/auto/parse_output.rb +++ b/auto/parse_output.rb @@ -18,11 +18,11 @@ class ParseOutput def initialize - @test_flag = false @xml_out = false @array_list = false + @class_name = 0 + @test_suite = nil @total_tests = false - @class_index = false end # Set the flag to indicate if there will be an XML output file or not @@ -30,7 +30,7 @@ class ParseOutput @xml_out = true end - # if write our output to XML + # If write our output to XML def write_xml_output output = File.open('report.xml', 'w') output << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" @@ -40,12 +40,10 @@ class ParseOutput output << "</testsuite>\n" end - # This function will try and determine when the suite is changed. This is + # This function will try and determine when the suite is changed. This is # is the name that gets added to the classname parameter. def test_suite_verify(test_suite_name) - return if @test_flag - @test_flag = true # Split the path name test_name = if @class_name == 1 test_suite_name.split('\\') # Windows @@ -53,10 +51,15 @@ class ParseOutput test_suite_name.split('/') # Unix based end - # Remove the extension - base_name = test_name[test_name.size - 1].split('.') - @test_suite = 'test.' + base_name[0] - printf "New Test: %s\n", @test_suite + # Remove the extension and extract the base_name + base_name = test_name[test_name.size - 1].split('.')[0] + + # Is this a new test suite? + if base_name.to_s != @test_suite.to_s + @test_suite = base_name + printf "New Test: %s\n", @test_suite + end + end # Test was flagged as having passed so format the output @@ -77,7 +80,8 @@ class ParseOutput test_suite = array[0].sub('TEST(', '') test_suite = test_suite.sub(',', '') test_name = array[1].sub(')', '') - + test_suite_verify(array[@class_name]) + printf "%-40s PASS\n", test_name return unless @xml_out @array_list.push ' <testcase classname="' + test_suite + '" name="' + test_name + '"/>' @@ -88,16 +92,21 @@ class ParseOutput last_item = array.length - 1 test_name = array[last_item - 2] reason = array[last_item].chomp.lstrip - test_suite_verify(array[@class_name]) - printf "%-40s IGNORED\n", test_name + class_name = array[@class_name] if test_name.start_with? 'TEST(' array2 = test_name.split(' ') - @test_suite = array2[0].sub('TEST(', '') - @test_suite = @test_suite.sub(',', '') + + test_suite = array2[0].sub('TEST(', '') + test_suite = test_suite.sub(',', '') + class_name = test_suite + test_name = array2[1].sub(')', '') end + test_suite_verify(class_name) + printf "%-40s IGNORED\n", test_name + return unless @xml_out @array_list.push ' <testcase classname="' + @test_suite + '" name="' + test_name + '">' @@ -110,16 +119,21 @@ class ParseOutput last_item = array.length - 1 test_name = array[last_item - 2] reason = array[last_item].chomp.lstrip + ' at line: ' + array[last_item - 3] - test_suite_verify(array[@class_name]) - printf "%-40s FAILED\n", test_name + class_name = array[@class_name] if test_name.start_with? 'TEST(' array2 = test_name.split(' ') - @test_suite = array2[0].sub('TEST(', '') - @test_suite = @test_suite.sub(',', '') + + test_suite = array2[0].sub('TEST(', '') + test_suite = test_suite.sub(',', '') + class_name = test_suite + test_name = array2[1].sub(')', '') end + test_suite_verify(class_name) + printf "%-40s FAILED\n", test_name + return unless @xml_out @array_list.push ' <testcase classname="' + @test_suite + '" name="' + test_name + '">' @@ -144,7 +158,6 @@ class ParseOutput # Main function used to parse the file that was captured. def process(name) - @test_flag = false @array_list = [] detect_os @@ -189,13 +202,7 @@ class ParseOutput test_passed_unity_fixture(line_array) test_pass += 1 end - # If none of the keywords are found there are no more tests for this suite so clear - # the test flag - else - @test_flag = false end - else - @test_flag = false end end puts '' @@ -208,7 +215,7 @@ class ParseOutput return unless @xml_out - heading = '<testsuite name="' + @test_suite.to_s + '" tests="' + @total_tests.to_s + '" failures="' + test_fail.to_s + '"' + ' skips="' + test_ignore.to_s + '">' + heading = '<testsuite name="Unity" tests="' + @total_tests.to_s + '" failures="' + test_fail.to_s + '"' + ' skips="' + test_ignore.to_s + '">' @array_list.insert(0, heading) write_xml_output end From ea51e2b35cb10fad3cbdc101142543daf96ea80f Mon Sep 17 00:00:00 2001 From: Fabian Zahn <fabian.zahn@gmail.com> Date: Thu, 22 Feb 2018 21:21:32 +0100 Subject: [PATCH 2/5] Refactored the os specific settings, it is now possible to convert both styles on every system (and even mixed) --- auto/parse_output.rb | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/auto/parse_output.rb b/auto/parse_output.rb index 33d1f71..02adbdc 100644 --- a/auto/parse_output.rb +++ b/auto/parse_output.rb @@ -23,6 +23,7 @@ class ParseOutput @class_name = 0 @test_suite = nil @total_tests = false + @path_delim = nil end # Set the flag to indicate if there will be an XML output file or not @@ -45,11 +46,7 @@ class ParseOutput def test_suite_verify(test_suite_name) # Split the path name - test_name = if @class_name == 1 - test_suite_name.split('\\') # Windows - else - test_suite_name.split('/') # Unix based - end + test_name = test_suite_name.split(@path_delim) # Remove the extension and extract the base_name base_name = test_name[test_name.size - 1].split('.')[0] @@ -59,7 +56,6 @@ class ParseOutput @test_suite = base_name printf "New Test: %s\n", @test_suite end - end # Test was flagged as having passed so format the output @@ -141,27 +137,24 @@ class ParseOutput @array_list.push ' </testcase>' end - # Figure out what OS we are running on. For now we are assuming if it's not Windows it must - # be Unix based. - def detect_os - os = RUBY_PLATFORM.split('-') - @class_name = if os.size == 2 - if os[1] == 'mingw32' - 1 - else - 0 - end - else - 0 - end + # Adjusts the os specific members according to the current path style + # (Windows or Unix based) + def set_os_specifics(line) + if line.include? '\\' + # Windows X:\Y\Z + @class_name = 1 + @path_delim = '\\' + else + # Unix Based /X/Y/Z + @class_name = 0 + @path_delim = '/' + end end # Main function used to parse the file that was captured. def process(name) @array_list = [] - detect_os - puts 'Parsing file: ' + name test_pass = 0 @@ -177,6 +170,7 @@ class ParseOutput # <path>/<test_file>.c:115:test_tc5100_initCanVoidPtrs:PASS # # where path is different on Unix vs Windows devices (Windows leads with a drive letter) + set_os_specifics(line) line_array = line.split(':') # If we were able to split the line then we can look to see if any of our target words From 7a437665864a99320e88606bb6e901a64bc763da Mon Sep 17 00:00:00 2001 From: Fabian Zahn <fabian.zahn@gmail.com> Date: Thu, 22 Feb 2018 21:33:11 +0100 Subject: [PATCH 3/5] - Fixed whitespaces and formatting - Added more expressiveness to the code - Fixed some of the rubocop hints --- auto/parse_output.rb | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/auto/parse_output.rb b/auto/parse_output.rb index 02adbdc..edb69fb 100644 --- a/auto/parse_output.rb +++ b/auto/parse_output.rb @@ -1,21 +1,22 @@ #============================================================ # Author: John Theofanopoulos -# A simple parser. Takes the output files generated during the build process and -# extracts information relating to the tests. +# A simple parser. Takes the output files generated during the +# build process and extracts information relating to the tests. # # Notes: # To capture an output file under VS builds use the following: # devenv [build instructions] > Output.txt & type Output.txt # -# To capture an output file under GCC/Linux builds use the following: +# To capture an output file under Linux builds use the following: # make | tee Output.txt # # To use this parser use the following command # ruby parseOutput.rb [options] [file] # options: -xml : produce a JUnit compatible XML file -# file : file to scan for results +# file: file to scan for results #============================================================ +# Parser class for handling the input file class ParseOutput def initialize @xml_out = false @@ -26,12 +27,12 @@ class ParseOutput @path_delim = nil end - # Set the flag to indicate if there will be an XML output file or not + # Set the flag to indicate if there will be an XML output file or not def set_xml_output @xml_out = true end - # If write our output to XML + # If write our output to XML def write_xml_output output = File.open('report.xml', 'w') output << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" @@ -44,18 +45,17 @@ class ParseOutput # This function will try and determine when the suite is changed. This is # is the name that gets added to the classname parameter. def test_suite_verify(test_suite_name) - # Split the path name test_name = test_suite_name.split(@path_delim) # Remove the extension and extract the base_name base_name = test_name[test_name.size - 1].split('.')[0] - # Is this a new test suite? - if base_name.to_s != @test_suite.to_s - @test_suite = base_name - printf "New Test: %s\n", @test_suite - end + # Return if the test suite hasn't changed + return unless base_name.to_s != @test_suite.to_s + + @test_suite = base_name + printf "New Test: %s\n", @test_suite end # Test was flagged as having passed so format the output @@ -78,6 +78,7 @@ class ParseOutput test_name = array[1].sub(')', '') test_suite_verify(array[@class_name]) printf "%-40s PASS\n", test_name + return unless @xml_out @array_list.push ' <testcase classname="' + test_suite + '" name="' + test_name + '"/>' @@ -152,10 +153,10 @@ class ParseOutput end # Main function used to parse the file that was captured. - def process(name) + def process(file_name) @array_list = [] - puts 'Parsing file: ' + name + puts 'Parsing file: ' + file_name test_pass = 0 test_fail = 0 @@ -163,7 +164,7 @@ class ParseOutput puts '' puts '=================== RESULTS =====================' puts '' - File.open(name).each do |line| + File.open(file_name).each do |line| # Typical test lines look like this: # <path>/<test_file>.c:36:test_tc1000_opsys:FAIL: Expected 1 Was 0 # <path>/<test_file>.c:112:test_tc5004_initCanChannel:IGNORE: Not Yet Implemented @@ -219,11 +220,11 @@ end parse_my_file = ParseOutput.new if ARGV.size >= 1 - ARGV.each do |a| - if a == '-xml' + ARGV.each do |arg| + if arg == '-xml' parse_my_file.set_xml_output else - parse_my_file.process(a) + parse_my_file.process(arg) break end end From 1994bf9d68d792ece33f2750d62bb7c361f12ed8 Mon Sep 17 00:00:00 2001 From: Fabian Zahn <fabian.zahn@gmail.com> Date: Mon, 26 Feb 2018 22:13:29 +0100 Subject: [PATCH 4/5] Fixed unity fixture output and added methods for each of the different outputs. Added documentation. Fixed some whitespaces. Refactored class_name to class_name_idx (expressiveness). Refactored the xml output to methods (extensibility). --- auto/parse_output.rb | 238 ++++++++++++++++++++++++++++--------------- 1 file changed, 156 insertions(+), 82 deletions(-) diff --git a/auto/parse_output.rb b/auto/parse_output.rb index edb69fb..cbafd7c 100644 --- a/auto/parse_output.rb +++ b/auto/parse_output.rb @@ -1,30 +1,38 @@ #============================================================ -# Author: John Theofanopoulos -# A simple parser. Takes the output files generated during the +# Author: John Theofanopoulos +# A simple parser. Takes the output files generated during the # build process and extracts information relating to the tests. # # Notes: # To capture an output file under VS builds use the following: -# devenv [build instructions] > Output.txt & type Output.txt +# devenv [build instructions] > Output.txt & type Output.txt # # To capture an output file under Linux builds use the following: # make | tee Output.txt # # To use this parser use the following command # ruby parseOutput.rb [options] [file] -# options: -xml : produce a JUnit compatible XML file -# file: file to scan for results +# options: -xml : produce a JUnit compatible XML file +# file: file to scan for results #============================================================ # Parser class for handling the input file class ParseOutput def initialize + # internal data + @class_name_idx = 0 + @path_delim = nil + + # xml output related @xml_out = false @array_list = false - @class_name = 0 + + # current suite name and statistics @test_suite = nil - @total_tests = false - @path_delim = nil + @total_tests = 0 + @test_passed = 0 + @test_failed = 0 + @test_ignored = 0 end # Set the flag to indicate if there will be an XML output file or not @@ -39,7 +47,34 @@ class ParseOutput @array_list.each do |item| output << item << "\n" end - output << "</testsuite>\n" + end + + # Pushes the suite info as xml to the array list, which will be written later + def push_xml_output_suite_info + # Insert opening tag at front + heading = '<testsuite name="Unity" tests="' + @total_tests.to_s + '" failures="' + @test_failed.to_s + '"' + ' skips="' + @test_ignored.to_s + '">' + @array_list.insert(0, heading) + # Push back the closing tag + @array_list.push '</testsuite>' + end + + # Pushes xml output data to the array list, which will be written later + def push_xml_output_passed(test_name) + @array_list.push ' <testcase classname="' + @test_suite + '" name="' + test_name + '"/>' + end + + # Pushes xml output data to the array list, which will be written later + def push_xml_output_failed(test_name, reason) + @array_list.push ' <testcase classname="' + @test_suite + '" name="' + test_name + '">' + @array_list.push ' <failure type="ASSERT FAILED">' + reason + '</failure>' + @array_list.push ' </testcase>' + end + + # Pushes xml output data to the array list, which will be written later + def push_xml_output_ignored(test_name, reason) + @array_list.push ' <testcase classname="' + @test_suite + '" name="' + test_name + '">' + @array_list.push ' <skipped type="TEST IGNORED">' + reason + '</skipped>' + @array_list.push ' </testcase>' end # This function will try and determine when the suite is changed. This is @@ -58,65 +93,76 @@ class ParseOutput printf "New Test: %s\n", @test_suite end - # Test was flagged as having passed so format the output - def test_passed(array) - last_item = array.length - 1 - test_name = array[last_item - 1] - test_suite_verify(array[@class_name]) - printf "%-40s PASS\n", test_name - - return unless @xml_out - - @array_list.push ' <testcase classname="' + @test_suite + '" name="' + test_name + '"/>' + # Prepares the line for verbose fixture output ("-v") + def prepare_fixture_line(line) + line = line.sub('IGNORE_TEST(', '') + line = line.sub('TEST(', '') + line = line.sub(')', ',') + line = line.chomp + array = line.split(',') + array.map { |x| x.to_s.lstrip.chomp } end # Test was flagged as having passed so format the output. # This is using the Unity fixture output and not the original Unity output. def test_passed_unity_fixture(array) - test_suite = array[0].sub('TEST(', '') - test_suite = test_suite.sub(',', '') - test_name = array[1].sub(')', '') - test_suite_verify(array[@class_name]) + class_name = array[0] + test_name = array[1] + test_suite_verify(class_name) + printf "%-40s PASS\n", test_name + + push_xml_output_passed(test_name) if @xml_out + end + + # Test was flagged as having failed so format the output. + # This is using the Unity fixture output and not the original Unity output. + def test_failed_unity_fixture(array) + class_name = array[0] + test_name = array[1] + test_suite_verify(class_name) + reason_array = array[2].split(':') + reason = reason_array[-1].lstrip.chomp + ' at line: ' + reason_array[-4] + + printf "%-40s FAILED\n", test_name + + push_xml_output_failed(test_name, reason) if @xml_out + end + + # Test was flagged as being ignored so format the output. + # This is using the Unity fixture output and not the original Unity output. + def test_ignored_unity_fixture(array) + class_name = array[0] + test_name = array[1] + reason = 'No reason given' + if array.size > 2 + reason_array = array[2].split(':') + tmp_reason = reason_array[-1].lstrip.chomp + reason = tmp_reason == 'IGNORE' ? 'No reason given' : tmp_reason + end + test_suite_verify(class_name) + printf "%-40s IGNORED\n", test_name + + push_xml_output_ignored(test_name, reason) if @xml_out + end + + # Test was flagged as having passed so format the output + def test_passed(array) + last_item = array.length - 1 + test_name = array[last_item - 1] + test_suite_verify(array[@class_name_idx]) printf "%-40s PASS\n", test_name return unless @xml_out - @array_list.push ' <testcase classname="' + test_suite + '" name="' + test_name + '"/>' + push_xml_output_passed(test_name) if @xml_out end - # Test was flagged as being ignored so format the output - def test_ignored(array) - last_item = array.length - 1 - test_name = array[last_item - 2] - reason = array[last_item].chomp.lstrip - class_name = array[@class_name] - - if test_name.start_with? 'TEST(' - array2 = test_name.split(' ') - - test_suite = array2[0].sub('TEST(', '') - test_suite = test_suite.sub(',', '') - class_name = test_suite - - test_name = array2[1].sub(')', '') - end - - test_suite_verify(class_name) - printf "%-40s IGNORED\n", test_name - - return unless @xml_out - - @array_list.push ' <testcase classname="' + @test_suite + '" name="' + test_name + '">' - @array_list.push ' <skipped type="TEST IGNORED">' + reason + '</skipped>' - @array_list.push ' </testcase>' - end - - # Test was flagged as having failed so format the line + # Test was flagged as having failed so format the line def test_failed(array) last_item = array.length - 1 test_name = array[last_item - 2] reason = array[last_item].chomp.lstrip + ' at line: ' + array[last_item - 3] - class_name = array[@class_name] + class_name = array[@class_name_idx] if test_name.start_with? 'TEST(' array2 = test_name.split(' ') @@ -131,11 +177,30 @@ class ParseOutput test_suite_verify(class_name) printf "%-40s FAILED\n", test_name - return unless @xml_out + push_xml_output_failed(test_name, reason) if @xml_out + end - @array_list.push ' <testcase classname="' + @test_suite + '" name="' + test_name + '">' - @array_list.push ' <failure type="ASSERT FAILED">' + reason + '</failure>' - @array_list.push ' </testcase>' + # Test was flagged as being ignored so format the output + def test_ignored(array) + last_item = array.length - 1 + test_name = array[last_item - 2] + reason = array[last_item].chomp.lstrip + class_name = array[@class_name_idx] + + if test_name.start_with? 'TEST(' + array2 = test_name.split(' ') + + test_suite = array2[0].sub('TEST(', '') + test_suite = test_suite.sub(',', '') + class_name = test_suite + + test_name = array2[1].sub(')', '') + end + + test_suite_verify(class_name) + printf "%-40s IGNORED\n", test_name + + push_xml_output_ignored(test_name, reason) if @xml_out end # Adjusts the os specific members according to the current path style @@ -143,11 +208,11 @@ class ParseOutput def set_os_specifics(line) if line.include? '\\' # Windows X:\Y\Z - @class_name = 1 + @class_name_idx = 1 @path_delim = '\\' else # Unix Based /X/Y/Z - @class_name = 0 + @class_name_idx = 0 @path_delim = '/' end end @@ -158,9 +223,9 @@ class ParseOutput puts 'Parsing file: ' + file_name - test_pass = 0 - test_fail = 0 - test_ignore = 0 + @test_passed = 0 + @test_failed = 0 + @test_ignored = 0 puts '' puts '=================== RESULTS =====================' puts '' @@ -175,43 +240,52 @@ class ParseOutput line_array = line.split(':') # If we were able to split the line then we can look to see if any of our target words - # were found. Case is important. - if (line_array.size >= 4) || (line.start_with? 'TEST(') - # Determine if this test passed - if line.include? ':PASS' + # were found. Case is important. + if (line_array.size >= 4) || (line.start_with? 'TEST(') || (line.start_with? 'IGNORE_TEST(') + + # check if the output is fixture output (with verbose flag "-v") + if (line.start_with? 'TEST(') || (line.start_with? 'IGNORE_TEST(') + line_array = prepare_fixture_line(line) + if line.include? ' PASS' + test_passed_unity_fixture(line_array) + @test_passed += 1 + elsif line.include? 'FAIL' + test_failed_unity_fixture(line_array) + @test_failed += 1 + elsif line.include? 'IGNORE' + test_ignored_unity_fixture(line_array) + @test_ignored += 1 + end + # normal output / fixture output (without verbose "-v") + elsif line.include? ':PASS' test_passed(line_array) - test_pass += 1 + @test_passed += 1 elsif line.include? ':FAIL' test_failed(line_array) - test_fail += 1 + @test_failed += 1 elsif line.include? ':IGNORE:' test_ignored(line_array) - test_ignore += 1 + @test_ignored += 1 elsif line.include? ':IGNORE' line_array.push('No reason given') test_ignored(line_array) - test_ignore += 1 - elsif line.start_with? 'TEST(' - if line.include? ' PASS' - line_array = line.split(' ') - test_passed_unity_fixture(line_array) - test_pass += 1 - end + @test_ignored += 1 end + @total_tests = @test_passed + @test_failed + @test_ignored end end puts '' puts '=================== SUMMARY =====================' puts '' - puts 'Tests Passed : ' + test_pass.to_s - puts 'Tests Failed : ' + test_fail.to_s - puts 'Tests Ignored : ' + test_ignore.to_s - @total_tests = test_pass + test_fail + test_ignore + puts 'Tests Passed : ' + @test_passed.to_s + puts 'Tests Failed : ' + @test_failed.to_s + puts 'Tests Ignored : ' + @test_ignored.to_s return unless @xml_out - heading = '<testsuite name="Unity" tests="' + @total_tests.to_s + '" failures="' + test_fail.to_s + '"' + ' skips="' + test_ignore.to_s + '">' - @array_list.insert(0, heading) + # push information about the suite + push_xml_output_suite_info + # write xml output file write_xml_output end end From 38e1ee872ccf2989ff873415ce98fd20ba9c2f1a Mon Sep 17 00:00:00 2001 From: Fabian Zahn <fabian.zahn@gmail.com> Date: Tue, 27 Feb 2018 07:23:18 +0100 Subject: [PATCH 5/5] Added some useful documentation which states the output formats that are parseable by this script. --- auto/parse_output.rb | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/auto/parse_output.rb b/auto/parse_output.rb index cbafd7c..f04508f 100644 --- a/auto/parse_output.rb +++ b/auto/parse_output.rb @@ -10,6 +10,11 @@ # To capture an output file under Linux builds use the following: # make | tee Output.txt # +# This script can handle the following output formats: +# - normal output (raw unity) +# - fixture output (unity_fixture.h/.c) +# - fixture output with verbose flag set ("-v") +# # To use this parser use the following command # ruby parseOutput.rb [options] [file] # options: -xml : produce a JUnit compatible XML file @@ -230,12 +235,25 @@ class ParseOutput puts '=================== RESULTS =====================' puts '' File.open(file_name).each do |line| - # Typical test lines look like this: + # Typical test lines look like these: + # ---------------------------------------------------- + # 1. normal output: # <path>/<test_file>.c:36:test_tc1000_opsys:FAIL: Expected 1 Was 0 # <path>/<test_file>.c:112:test_tc5004_initCanChannel:IGNORE: Not Yet Implemented # <path>/<test_file>.c:115:test_tc5100_initCanVoidPtrs:PASS # - # where path is different on Unix vs Windows devices (Windows leads with a drive letter) + # 2. fixture output + # <path>/<test_file>.c:63:TEST(<test_group>, <test_function>):FAIL: Expected 0x00001234 Was 0x00005A5A + # <path>/<test_file>.c:36:TEST(<test_group>, <test_function>):IGNORE + # Note: "PASS" information won't be generated in this mode + # + # 3. fixture output with verbose information ("-v") + # TEST(<test_group, <test_file>)<path>/<test_file>:168::FAIL: Expected 0x8D Was 0x8C + # TEST(<test_group>, <test_file>)<path>/<test_file>:22::IGNORE: This Test Was Ignored On Purpose + # IGNORE_TEST(<test_group, <test_file>) + # TEST(<test_group, <test_file>) PASS + # + # Note: Where path is different on Unix vs Windows devices (Windows leads with a drive letter)! set_os_specifics(line) line_array = line.split(':')